bytesmemorystaticData=abi.encodePacked(id,owner);// This is a custom function as Solidity does not provide a way to tightly pack array elements
// 这是一个自定义函数,因为 Solidity 没有提供紧密打包数组元素的方法
bytesmemorypackedScores=packElementsWithoutPadding(scores);// abi.encodePacked concatenates both description and packedScores without including their lengths
// abi.encodePacked 连接 description 和 packedScores,但不包括它们的长度
bytesmemorydynamicData=abi.encodePacked(description,packedScores);// Total length is encoded in the 56 least significant bits
// 总长度编码在 56 个最低有效位中
EncodedLengthsencodedLengths=dynamicData.length;// Each length is encoded using 5 bytes
// 每个长度都使用 5 个字节进行编码
encodedLengths|=(description.length<<(56));encodedLengths|=(encodedData.length<<(56+8*5));// The full encoded record data is represented by the following tuple:
// 完整的编码记录数据由以下元组表示:
// (staticData, encodedLengths, dynamicData)
存储接口
所有存储必须实现以下接口。
interfaceIStore{/**
* Get full encoded record (all fields, static and dynamic data) for the given tableId and key tuple.
* 获取给定 tableId 和键元组的完整编码记录(所有字段,静态和动态数据)。
*/functiongetRecord(ResourceIdtableId,bytes32[]calldatakeyTuple)externalviewreturns(bytesmemorystaticData,EncodedLengthsencodedLengths,bytesmemorydynamicData);/**
* Get a single encoded field from the given tableId and key tuple.
* 从给定的 tableId 和键元组中获取单个编码字段。
*/functiongetField(ResourceIdtableId,bytes32[]calldatakeyTuple,uint8fieldIndex)externalviewreturns(bytesmemorydata);/**
* Get the byte length of a single field from the given tableId and key tuple
* 从给定的 tableId 和键元组中获取单个字段的字节长度。
*/functiongetFieldLength(ResourceIdtableId,bytes32[]memorykeyTuple,uint8fieldIndex)externalviewreturns(uint256);}
// First two bytes indicates that this is an on-chain table
// 前两个字节表示这是一个链上表格
// The next 30 bytes are the unique identifier for the Tables table
// 接下来的 30 个字节是 Tables 表格的唯一标识符
// bytes32("tb") | bytes32("store") >> (2 * 8) | bytes32("Tables") >> (2 * 8 + 14 * 8)
ResourceIdtableId=ResourceId.wrap(0x746273746f72650000000000000000005461626c657300000000000000000000);
通过为表格注册重用现有的 Store 抽象,我们还可以简化实现并消除对其他特定表格注册事件的需求。索引器可以利用标准的 Store 事件来访问模式信息,从而确保一致性并降低复杂性。
参考实现
存储事件索引
以下示例显示了一个简单的内存索引器如何使用 Store 事件来复制链下的 Store 状态。重要的是要注意,此索引器对原始编码数据进行操作,其本身并不是很有用,但可以改进,我们将在下一节中解释。
我们在此示例中使用 TypeScript,但可以使用其他语言轻松地复制它。
typeHex=`0x${string}`;typeRecord={staticData:Hex;encodedLengths:Hex;dynamicData:Hex;};conststore=newMap<string,Record>();// Create a key string from a table ID and key tuple to use in our store Map above// 从表 ID 和键元组创建一个键字符串,以便在我们上面的 store Map 中使用functionstoreKey(tableId:Hex,keyTuple:Hex[]):string{return`${tableId}:${keyTuple.join(",")}`;}// Like `Array.splice`, but for strings of bytes// 类似于 `Array.splice`,但用于字节字符串functionbytesSplice(data:Hex,start:number,deleteCount=0,newData:Hex="0x"):Hex{constdataNibbles=data.replace(/^0x/,"").split("");constnewDataNibbles=newData.replace(/^0x/,"").split("");return`0x${dataNibbles.splice(start,deleteCount*2).concat(newDataNibbles).join("")}`;}functionbytesLength(data:Hex):number{returndata.replace(/^0x/,"").length/2;}functionprocessStoreEvent(log:StoreEvent){if(log.eventName==="Store_SetRecord"){constkey=storeKey(log.args.tableId,log.args.keyTuple);// Overwrite all of the Record's fields// 覆盖 Record 的所有字段store.set(key,{staticData:log.args.staticData,encodedLengths:log.args.encodedLengths,dynamicData:log.args.dynamicData,});}elseif(log.eventName==="Store_SpliceStaticData"){constkey=storeKey(log.args.tableId,log.args.keyTuple);constrecord=store.get(key)??{staticData:"0x",encodedLengths:"0x",dynamicData:"0x",};// Splice the static field data of the Record// 拼接 Record 的静态字段数据store.set(key,{staticData:bytesSplice(record.staticData,log.args.start,bytesLength(log.args.data),log.args.data),encodedLengths:record.encodedLengths,dynamicData:record.dynamicData,});}elseif(log.eventName==="Store_SpliceDynamicData"){constkey=storeKey(log.args.tableId,log.args.keyTuple);constrecord=store.get(key)??{staticData:"0x",encodedLengths:"0x",dynamicData:"0x",};// Splice the dynamic field data of the Record// 拼接 Record 的动态字段数据store.set(key,{staticData:record.staticData,encodedLengths:log.args.encodedLengths,dynamicData:bytesSplice(record.dynamicData,log.args.start,log.args.deleteCount,log.args.data),});}elseif(log.eventName==="Store_DeleteRecord"){constkey=storeKey(log.args.tableId,log.args.keyTuple);// Delete the whole Record// 删除整个 Recordstore.delete(key);}}
安全考虑
访问控制
本标准仅定义从 Store 读取数据的函数(getRecord、getField 和 getFieldLength)。用于在存储中设置或修改记录的方法留给每个特定的实现。因此,实现必须提供适当的访问控制机制来写入存储,并根据其特定用例进行定制。