diff --git a/.changeset/hip-tables-check.md b/.changeset/hip-tables-check.md new file mode 100644 index 0000000000..704341d407 --- /dev/null +++ b/.changeset/hip-tables-check.md @@ -0,0 +1,13 @@ +--- +"@latticexyz/store": major +"@latticexyz/world": major +--- + +Store's `getRecord` has been updated to return `staticData`, `encodedLengths`, and `dynamicData` instead of a single `data` blob, to match the new behaviour of Store setter methods. + +If you use codegenerated libraries, you will only need to update `encode` calls. + +```diff +- bytes memory data = Position.encode(x, y); ++ (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) = Position.encode(x, y); +``` diff --git a/e2e/packages/contracts/src/codegen/tables/Multi.sol b/e2e/packages/contracts/src/codegen/tables/Multi.sol index fd30c691ef..abab8c9590 100644 --- a/e2e/packages/contracts/src/codegen/tables/Multi.sol +++ b/e2e/packages/contracts/src/codegen/tables/Multi.sol @@ -233,8 +233,12 @@ library Multi { _keyTuple[2] = bytes32(uint256(c)); _keyTuple[3] = bytes32(uint256(int256(d))); - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -245,8 +249,12 @@ library Multi { _keyTuple[2] = bytes32(uint256(c)); _keyTuple[3] = bytes32(uint256(int256(d))); - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -257,8 +265,12 @@ library Multi { _keyTuple[2] = bytes32(uint256(c)); _keyTuple[3] = bytes32(uint256(int256(d))); - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -324,11 +336,26 @@ library Multi { set(_store, a, b, c, d, _table.num, _table.value); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (MultiData memory _table) { - _table.num = (int256(uint256(Bytes.slice32(_blob, 0)))); + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (int256 num, bool value) { + num = (int256(uint256(Bytes.slice32(_blob, 0)))); - _table.value = (_toBool(uint8(Bytes.slice1(_blob, 32)))); + value = (_toBool(uint8(Bytes.slice1(_blob, 32)))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (MultiData memory _table) { + (_table.num, _table.value) = decodeStatic(_staticData); } /** Tightly pack static data using this table's schema */ @@ -337,13 +364,13 @@ library Multi { } /** Tightly pack full data using this table's field layout */ - function encode(int256 num, bool value) internal pure returns (bytes memory) { + function encode(int256 num, bool value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(num, value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/Number.sol b/e2e/packages/contracts/src/codegen/tables/Number.sol index 77dcf598c3..b75e730224 100644 --- a/e2e/packages/contracts/src/codegen/tables/Number.sol +++ b/e2e/packages/contracts/src/codegen/tables/Number.sol @@ -131,13 +131,13 @@ library Number { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 value) internal pure returns (bytes memory) { + function encode(uint32 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/NumberList.sol b/e2e/packages/contracts/src/codegen/tables/NumberList.sol index 2703550074..7b03f525db 100644 --- a/e2e/packages/contracts/src/codegen/tables/NumberList.sol +++ b/e2e/packages/contracts/src/codegen/tables/NumberList.sol @@ -285,12 +285,12 @@ library NumberList { } /** Tightly pack full data using this table's field layout */ - function encode(uint32[] memory value) internal pure returns (bytes memory) { + function encode(uint32[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/e2e/packages/contracts/src/codegen/tables/Vector.sol b/e2e/packages/contracts/src/codegen/tables/Vector.sol index 380f222702..ee5dd92631 100644 --- a/e2e/packages/contracts/src/codegen/tables/Vector.sol +++ b/e2e/packages/contracts/src/codegen/tables/Vector.sol @@ -188,8 +188,12 @@ library Vector { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(uint256(key)); - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -197,8 +201,12 @@ library Vector { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(uint256(key)); - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -206,8 +214,12 @@ library Vector { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(uint256(key)); - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -264,11 +276,26 @@ library Vector { set(_store, key, _table.x, _table.y); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (VectorData memory _table) { - _table.x = (int32(uint32(Bytes.slice4(_blob, 0)))); + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (int32 x, int32 y) { + x = (int32(uint32(Bytes.slice4(_blob, 0)))); - _table.y = (int32(uint32(Bytes.slice4(_blob, 4)))); + y = (int32(uint32(Bytes.slice4(_blob, 4)))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (VectorData memory _table) { + (_table.x, _table.y) = decodeStatic(_staticData); } /** Tightly pack static data using this table's schema */ @@ -277,13 +304,13 @@ library Vector { } /** Tightly pack full data using this table's field layout */ - function encode(int32 x, int32 y) internal pure returns (bytes memory) { + function encode(int32 x, int32 y) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(x, y); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol b/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol index 8cff969735..21056806b4 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/CounterTable.sol @@ -123,13 +123,13 @@ library CounterTable { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 value) internal pure returns (bytes memory) { + function encode(uint32 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol b/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol index 8344c02274..38f67b2815 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/Inventory.sol @@ -147,13 +147,13 @@ library Inventory { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 amount) internal pure returns (bytes memory) { + function encode(uint32 amount) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(amount); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol b/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol index 9bcba034c6..48d6426ca7 100644 --- a/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol +++ b/examples/minimal/packages/contracts/src/codegen/tables/MessageTable.sol @@ -119,12 +119,12 @@ library MessageTable { } /** Tightly pack full data using this table's field layout */ - function encode(string memory value) internal pure returns (bytes memory) { + function encode(string memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Dynamics1.sol b/packages/cli/contracts/src/codegen/tables/Dynamics1.sol index 1b7dee5110..e2482100cd 100644 --- a/packages/cli/contracts/src/codegen/tables/Dynamics1.sol +++ b/packages/cli/contracts/src/codegen/tables/Dynamics1.sol @@ -1240,8 +1240,12 @@ library Dynamics1 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -1249,8 +1253,12 @@ library Dynamics1 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -1258,8 +1266,12 @@ library Dynamics1 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -1335,48 +1347,69 @@ library Dynamics1 { set(_store, key, _table.staticB32, _table.staticI32, _table.staticU128, _table.staticAddrs, _table.staticBools); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) + internal + pure + returns ( + bytes32[1] memory staticB32, + int32[2] memory staticI32, + uint128[3] memory staticU128, + address[4] memory staticAddrs, + bool[5] memory staticBools + ) + { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + staticB32 = toStaticArray_bytes32_1(SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + staticI32 = toStaticArray_int32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_int32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(2); + } + staticU128 = toStaticArray_uint128_3(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint128()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(3); + } + staticAddrs = toStaticArray_address_4(SliceLib.getSubslice(_blob, _start, _end).decodeArray_address()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(4); + } + staticBools = toStaticArray_bool_5(SliceLib.getSubslice(_blob, _start, _end).decodeArray_bool()); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ - function decode(bytes memory _blob) internal pure returns (Dynamics1Data memory _table) { - // 0 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 0)); - - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 0) { - // skip static data length + dynamic lengths word - uint256 _start = 32; - uint256 _end; - unchecked { - _end = 32 + _encodedLengths.atIndex(0); - } - _table.staticB32 = toStaticArray_bytes32_1(SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - _table.staticI32 = toStaticArray_int32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_int32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(2); - } - _table.staticU128 = toStaticArray_uint128_3(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint128()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(3); - } - _table.staticAddrs = toStaticArray_address_4(SliceLib.getSubslice(_blob, _start, _end).decodeArray_address()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(4); - } - _table.staticBools = toStaticArray_bool_5(SliceLib.getSubslice(_blob, _start, _end).decodeArray_bool()); - } + function decode( + bytes memory, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (Dynamics1Data memory _table) { + (_table.staticB32, _table.staticI32, _table.staticU128, _table.staticAddrs, _table.staticBools) = decodeDynamic( + _encodedLengths, + _dynamicData + ); } /** Tightly pack dynamic data using this table's schema */ @@ -1424,12 +1457,12 @@ library Dynamics1 { uint128[3] memory staticU128, address[4] memory staticAddrs, bool[5] memory staticBools - ) internal pure returns (bytes memory) { + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(staticB32, staticI32, staticU128, staticAddrs, staticBools); bytes memory _dynamicData = encodeDynamic(staticB32, staticI32, staticU128, staticAddrs, staticBools); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Dynamics2.sol b/packages/cli/contracts/src/codegen/tables/Dynamics2.sol index 77371f92a7..c587e088ea 100644 --- a/packages/cli/contracts/src/codegen/tables/Dynamics2.sol +++ b/packages/cli/contracts/src/codegen/tables/Dynamics2.sol @@ -749,8 +749,12 @@ library Dynamics2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -758,8 +762,12 @@ library Dynamics2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -767,8 +775,12 @@ library Dynamics2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -822,36 +834,44 @@ library Dynamics2 { set(_store, key, _table.u64, _table.str, _table.b); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) internal pure returns (uint64[] memory u64, string memory str, bytes memory b) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + u64 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint64()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + str = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(2); + } + b = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ - function decode(bytes memory _blob) internal pure returns (Dynamics2Data memory _table) { - // 0 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 0)); - - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 0) { - // skip static data length + dynamic lengths word - uint256 _start = 32; - uint256 _end; - unchecked { - _end = 32 + _encodedLengths.atIndex(0); - } - _table.u64 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint64()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - _table.str = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(2); - } - _table.b = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); - } + function decode( + bytes memory, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (Dynamics2Data memory _table) { + (_table.u64, _table.str, _table.b) = decodeDynamic(_encodedLengths, _dynamicData); } /** Tightly pack dynamic data using this table's schema */ @@ -872,12 +892,16 @@ library Dynamics2 { } /** Tightly pack full data using this table's field layout */ - function encode(uint64[] memory u64, string memory str, bytes memory b) internal pure returns (bytes memory) { + function encode( + uint64[] memory u64, + string memory str, + bytes memory b + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(u64, str, b); bytes memory _dynamicData = encodeDynamic(u64, str, b); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Ephemeral.sol b/packages/cli/contracts/src/codegen/tables/Ephemeral.sol index 04e78e2503..dae46bfb71 100644 --- a/packages/cli/contracts/src/codegen/tables/Ephemeral.sol +++ b/packages/cli/contracts/src/codegen/tables/Ephemeral.sol @@ -119,13 +119,13 @@ library Ephemeral { } /** Tightly pack full data using this table's field layout */ - function encode(uint256 value) internal pure returns (bytes memory) { + function encode(uint256 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Singleton.sol b/packages/cli/contracts/src/codegen/tables/Singleton.sol index 3db7f72067..5d8d466a6c 100644 --- a/packages/cli/contracts/src/codegen/tables/Singleton.sol +++ b/packages/cli/contracts/src/codegen/tables/Singleton.sol @@ -724,16 +724,24 @@ library Singleton { function get() internal view returns (int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ function _get() internal view returns (int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -742,8 +750,12 @@ library Singleton { ) internal view returns (int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -782,40 +794,54 @@ library Singleton { _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (int256 v1) { + v1 = (int256(uint256(Bytes.slice32(_blob, 0)))); + } + + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) internal pure returns (uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + v2 = toStaticArray_uint32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + v3 = toStaticArray_uint32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(2); + } + v4 = toStaticArray_uint32_1(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ function decode( - bytes memory _blob + bytes memory _staticData, + PackedCounter _encodedLengths, + bytes memory _dynamicData ) internal pure returns (int256 v1, uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4) { - // 32 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 32)); - - v1 = (int256(uint256(Bytes.slice32(_blob, 0)))); + (v1) = decodeStatic(_staticData); - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 32) { - // skip static data length + dynamic lengths word - uint256 _start = 64; - uint256 _end; - unchecked { - _end = 64 + _encodedLengths.atIndex(0); - } - v2 = toStaticArray_uint32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - v3 = toStaticArray_uint32_2(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(2); - } - v4 = toStaticArray_uint32_1(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); - } + (v2, v3, v4) = decodeDynamic(_encodedLengths, _dynamicData); } /** Tightly pack static data using this table's schema */ @@ -855,13 +881,13 @@ library Singleton { uint32[2] memory v2, uint32[2] memory v3, uint32[1] memory v4 - ) internal pure returns (bytes memory) { + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(v1); PackedCounter _encodedLengths = encodeLengths(v2, v3, v4); bytes memory _dynamicData = encodeDynamic(v2, v3, v4); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/cli/contracts/src/codegen/tables/Statics.sol b/packages/cli/contracts/src/codegen/tables/Statics.sol index b65c03db2f..8a911d7d46 100644 --- a/packages/cli/contracts/src/codegen/tables/Statics.sol +++ b/packages/cli/contracts/src/codegen/tables/Statics.sol @@ -657,8 +657,12 @@ library Statics { _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -678,8 +682,12 @@ library Statics { _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -700,8 +708,12 @@ library Statics { _keyTuple[4] = _boolToBytes32(k5); _keyTuple[5] = bytes32(uint256(uint8(k6))); - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -822,19 +834,36 @@ library Statics { set(_store, k1, k2, k3, k4, k5, k6, _table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (StaticsData memory _table) { - _table.v1 = (uint256(Bytes.slice32(_blob, 0))); + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic( + bytes memory _blob + ) internal pure returns (uint256 v1, int32 v2, bytes16 v3, address v4, bool v5, Enum1 v6) { + v1 = (uint256(Bytes.slice32(_blob, 0))); - _table.v2 = (int32(uint32(Bytes.slice4(_blob, 32)))); + v2 = (int32(uint32(Bytes.slice4(_blob, 32)))); - _table.v3 = (Bytes.slice16(_blob, 36)); + v3 = (Bytes.slice16(_blob, 36)); - _table.v4 = (address(Bytes.slice20(_blob, 52))); + v4 = (address(Bytes.slice20(_blob, 52))); - _table.v5 = (_toBool(uint8(Bytes.slice1(_blob, 72)))); + v5 = (_toBool(uint8(Bytes.slice1(_blob, 72)))); - _table.v6 = Enum1(uint8(Bytes.slice1(_blob, 73))); + v6 = Enum1(uint8(Bytes.slice1(_blob, 73))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (StaticsData memory _table) { + (_table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6) = decodeStatic(_staticData); } /** Tightly pack static data using this table's schema */ @@ -857,13 +886,13 @@ library Statics { address v4, bool v5, Enum1 v6 - ) internal pure returns (bytes memory) { + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5, v6); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 839855935a..d224bfcb03 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -165,13 +165,13 @@ "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "custom encode", - "gasUsed": 2065 + "gasUsed": 1571 }, { "file": "test/Gas.t.sol", "test": "testCompareAbiEncodeVsCustom", "name": "custom decode", - "gasUsed": 1981 + "gasUsed": 2020 }, { "file": "test/Gas.t.sol", @@ -375,7 +375,7 @@ "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "get record from Mixed", - "gasUsed": 7181 + "gasUsed": 7028 }, { "file": "test/PackedCounter.t.sol", @@ -633,7 +633,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access non-existing record", - "gasUsed": 6198 + "gasUsed": 7054 }, { "file": "test/StoreCoreGas.t.sol", @@ -645,7 +645,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access dynamic field of non-existing record", - "gasUsed": 2045 + "gasUsed": 2046 }, { "file": "test/StoreCoreGas.t.sol", @@ -657,7 +657,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access slice of dynamic field of non-existing record", - "gasUsed": 1484 + "gasUsed": 1483 }, { "file": "test/StoreCoreGas.t.sol", @@ -741,7 +741,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: register table", - "gasUsed": 609744 + "gasUsed": 609760 }, { "file": "test/StoreCoreGas.t.sol", @@ -771,7 +771,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "get complex record with dynamic data (4 slots)", - "gasUsed": 4469 + "gasUsed": 4216 }, { "file": "test/StoreCoreGas.t.sol", @@ -861,7 +861,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "get static record (1 slot)", - "gasUsed": 1608 + "gasUsed": 1524 }, { "file": "test/StoreCoreGas.t.sol", @@ -873,7 +873,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "get static record (2 slots)", - "gasUsed": 1796 + "gasUsed": 1709 }, { "file": "test/StoreCoreGas.t.sol", @@ -1095,7 +1095,7 @@ "file": "test/Vector2.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register Vector2 field layout", - "gasUsed": 411032 + "gasUsed": 411055 }, { "file": "test/Vector2.t.sol", @@ -1107,6 +1107,6 @@ "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "get Vector2 record", - "gasUsed": 2530 + "gasUsed": 2505 } ] diff --git a/packages/store/src/IStore.sol b/packages/store/src/IStore.sol index 767ec27b7b..d1709594f4 100644 --- a/packages/store/src/IStore.sol +++ b/packages/store/src/IStore.sol @@ -25,7 +25,7 @@ interface IStoreRead { bytes32 tableId, bytes32[] calldata keyTuple, FieldLayout fieldLayout - ) external view returns (bytes memory data); + ) external view returns (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData); /** * Get a single field from the given tableId and key tuple, with the given value field layout diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol index 67361e3d84..1f107f4789 100644 --- a/packages/store/src/StoreCore.sol +++ b/packages/store/src/StoreCore.sol @@ -521,54 +521,31 @@ library StoreCore { bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout - ) internal view returns (bytes memory) { + ) internal view returns (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) { // Get the static data length uint256 staticLength = fieldLayout.staticDataLength(); - uint256 outputLength = staticLength; - // Load the dynamic data length if there are dynamic fields - PackedCounter dynamicDataLength; + // Load the static data from storage + staticData = StoreCoreInternal._getStaticData(tableId, keyTuple, staticLength); + + // Load the dynamic data if there are dynamic fields uint256 numDynamicFields = fieldLayout.numDynamicFields(); if (numDynamicFields > 0) { - dynamicDataLength = StoreCoreInternal._loadEncodedDynamicDataLength(tableId, keyTuple); - // TODO should total output include dynamic data length even if it's 0? - if (dynamicDataLength.total() > 0) { - outputLength += 32 + dynamicDataLength.total(); // encoded length + data + // Load the encoded dynamic data length + encodedLengths = StoreCoreInternal._loadEncodedDynamicDataLength(tableId, keyTuple); + + // Append dynamic data + dynamicData = new bytes(encodedLengths.total()); + uint256 memoryPointer = Memory.dataPointer(dynamicData); + + for (uint8 i; i < numDynamicFields; i++) { + uint256 dynamicDataLocation = StoreCoreInternal._getDynamicDataLocation(tableId, keyTuple, i); + uint256 length = encodedLengths.atIndex(i); + Storage.load({ storagePointer: dynamicDataLocation, length: length, offset: 0, memoryPointer: memoryPointer }); + // Advance memoryPointer by the length of this dynamic field + memoryPointer += length; } } - - // Allocate length for the full packed data (static and dynamic) - bytes memory data = new bytes(outputLength); - uint256 memoryPointer = Memory.dataPointer(data); - - // Load the static data from storage - StoreCoreInternal._getStaticData(tableId, keyTuple, staticLength, memoryPointer); - - // Early return if there are no dynamic fields - if (dynamicDataLength.total() == 0) return data; - - // Advance memoryPointer to the dynamic data section - memoryPointer += staticLength; - - // Append the encoded dynamic length - assembly { - mstore(memoryPointer, dynamicDataLength) - } - // Advance memoryPointer by the length of `dynamicDataLength` (1 word) - memoryPointer += 0x20; - - // Append dynamic data - for (uint8 i; i < numDynamicFields; i++) { - uint256 dynamicDataLocation = StoreCoreInternal._getDynamicDataLocation(tableId, keyTuple, i); - uint256 length = dynamicDataLength.atIndex(i); - Storage.load({ storagePointer: dynamicDataLocation, length: length, offset: 0, memoryPointer: memoryPointer }); - - // Advance memoryPointer by the length of this dynamic field - memoryPointer += length; - } - - // Return the packed data - return data; } /** @@ -885,14 +862,13 @@ library StoreCoreInternal { function _getStaticData( bytes32 tableId, bytes32[] memory keyTuple, - uint256 length, - uint256 memoryPointer - ) internal view { - if (length == 0) return; + uint256 length + ) internal view returns (bytes memory) { + if (length == 0) return ""; // Load the data from storage uint256 location = _getStaticDataLocation(tableId, keyTuple); - Storage.load({ storagePointer: location, length: length, offset: 0, memoryPointer: memoryPointer }); + return Storage.load({ storagePointer: location, length: length, offset: 0 }); } /** diff --git a/packages/store/src/StoreRead.sol b/packages/store/src/StoreRead.sol index 82ea6e30fe..5d85bef3e6 100644 --- a/packages/store/src/StoreRead.sol +++ b/packages/store/src/StoreRead.sol @@ -6,6 +6,7 @@ import { IStoreRead } from "./IStore.sol"; import { StoreCore } from "./StoreCore.sol"; import { FieldLayout } from "./FieldLayout.sol"; import { Schema } from "./Schema.sol"; +import { PackedCounter } from "./PackedCounter.sol"; contract StoreRead is IStoreRead { function storeVersion() public pure returns (bytes32) { @@ -28,8 +29,8 @@ contract StoreRead is IStoreRead { bytes32 tableId, bytes32[] calldata keyTuple, FieldLayout fieldLayout - ) public view virtual returns (bytes memory data) { - data = StoreCore.getRecord(tableId, keyTuple, fieldLayout); + ) public view virtual returns (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) { + return StoreCore.getRecord(tableId, keyTuple, fieldLayout); } function getField( diff --git a/packages/store/src/StoreSwitch.sol b/packages/store/src/StoreSwitch.sol index 412d34bc5b..2206a5e6ff 100644 --- a/packages/store/src/StoreSwitch.sol +++ b/packages/store/src/StoreSwitch.sol @@ -7,6 +7,7 @@ import { IStoreHook } from "./IStoreHook.sol"; import { StoreCore } from "./StoreCore.sol"; import { Schema } from "./Schema.sol"; import { FieldLayout } from "./FieldLayout.sol"; +import { PackedCounter } from "./PackedCounter.sol"; /** * Call IStore functions on self or msg.sender, depending on whether the call is a delegatecall or regular call. @@ -221,7 +222,7 @@ library StoreSwitch { bytes32 tableId, bytes32[] memory keyTuple, FieldLayout fieldLayout - ) internal view returns (bytes memory) { + ) internal view returns (bytes memory, PackedCounter, bytes memory) { address _storeAddress = getStoreAddress(); if (_storeAddress == address(this)) { return StoreCore.getRecord(tableId, keyTuple, fieldLayout); diff --git a/packages/store/src/codegen/tables/Callbacks.sol b/packages/store/src/codegen/tables/Callbacks.sol index 499daf1a2a..148bbe1fb8 100644 --- a/packages/store/src/codegen/tables/Callbacks.sol +++ b/packages/store/src/codegen/tables/Callbacks.sol @@ -315,12 +315,12 @@ library Callbacks { } /** Tightly pack full data using this table's field layout */ - function encode(bytes24[] memory value) internal pure returns (bytes memory) { + function encode(bytes24[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Hooks.sol b/packages/store/src/codegen/tables/Hooks.sol index d5eaafb986..5695f5a9a4 100644 --- a/packages/store/src/codegen/tables/Hooks.sol +++ b/packages/store/src/codegen/tables/Hooks.sol @@ -312,12 +312,12 @@ library Hooks { } /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { + function encode(bytes21[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/KeyEncoding.sol b/packages/store/src/codegen/tables/KeyEncoding.sol index 013f19d845..4a5afcc886 100644 --- a/packages/store/src/codegen/tables/KeyEncoding.sol +++ b/packages/store/src/codegen/tables/KeyEncoding.sol @@ -205,13 +205,13 @@ library KeyEncoding { } /** Tightly pack full data using this table's field layout */ - function encode(bool value) internal pure returns (bytes memory) { + function encode(bool value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Mixed.sol b/packages/store/src/codegen/tables/Mixed.sol index fbadcc31ee..f4b9e76651 100644 --- a/packages/store/src/codegen/tables/Mixed.sol +++ b/packages/store/src/codegen/tables/Mixed.sol @@ -634,8 +634,12 @@ library Mixed { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -643,8 +647,12 @@ library Mixed { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -652,8 +660,12 @@ library Mixed { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -710,34 +722,50 @@ library Mixed { set(_store, key, _table.u32, _table.u128, _table.a32, _table.s); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint32 u32, uint128 u128) { + u32 = (uint32(Bytes.slice4(_blob, 0))); + + u128 = (uint128(Bytes.slice16(_blob, 4))); + } + + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) internal pure returns (uint32[] memory a32, string memory s) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + a32 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + s = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ - function decode(bytes memory _blob) internal pure returns (MixedData memory _table) { - // 20 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 20)); - - _table.u32 = (uint32(Bytes.slice4(_blob, 0))); - - _table.u128 = (uint128(Bytes.slice16(_blob, 4))); - - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 20) { - // skip static data length + dynamic lengths word - uint256 _start = 52; - uint256 _end; - unchecked { - _end = 52 + _encodedLengths.atIndex(0); - } - _table.a32 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - _table.s = (string(SliceLib.getSubslice(_blob, _start, _end).toBytes())); - } + function decode( + bytes memory _staticData, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (MixedData memory _table) { + (_table.u32, _table.u128) = decodeStatic(_staticData); + + (_table.a32, _table.s) = decodeDynamic(_encodedLengths, _dynamicData); } /** Tightly pack static data using this table's schema */ @@ -759,13 +787,18 @@ library Mixed { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 u32, uint128 u128, uint32[] memory a32, string memory s) internal pure returns (bytes memory) { + function encode( + uint32 u32, + uint128 u128, + uint32[] memory a32, + string memory s + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(u32, u128); PackedCounter _encodedLengths = encodeLengths(a32, s); bytes memory _dynamicData = encodeDynamic(a32, s); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/StoreHooks.sol b/packages/store/src/codegen/tables/StoreHooks.sol index 43ca3b5fc1..70e14eae9f 100644 --- a/packages/store/src/codegen/tables/StoreHooks.sol +++ b/packages/store/src/codegen/tables/StoreHooks.sol @@ -315,12 +315,12 @@ library StoreHooks { } /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { + function encode(bytes21[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Tables.sol b/packages/store/src/codegen/tables/Tables.sol index 268d19548c..dbf0de5289 100644 --- a/packages/store/src/codegen/tables/Tables.sol +++ b/packages/store/src/codegen/tables/Tables.sol @@ -702,8 +702,12 @@ library Tables { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = tableId; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -711,8 +715,12 @@ library Tables { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = tableId; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -720,8 +728,12 @@ library Tables { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = tableId; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -822,36 +834,54 @@ library Tables { ); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic( + bytes memory _blob + ) internal pure returns (bytes32 fieldLayout, bytes32 keySchema, bytes32 valueSchema) { + fieldLayout = (Bytes.slice32(_blob, 0)); + + keySchema = (Bytes.slice32(_blob, 32)); + + valueSchema = (Bytes.slice32(_blob, 64)); + } + + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) internal pure returns (bytes memory abiEncodedKeyNames, bytes memory abiEncodedFieldNames) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + abiEncodedKeyNames = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + abiEncodedFieldNames = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ - function decode(bytes memory _blob) internal pure returns (TablesData memory _table) { - // 96 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 96)); - - _table.fieldLayout = (Bytes.slice32(_blob, 0)); - - _table.keySchema = (Bytes.slice32(_blob, 32)); - - _table.valueSchema = (Bytes.slice32(_blob, 64)); - - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 96) { - // skip static data length + dynamic lengths word - uint256 _start = 128; - uint256 _end; - unchecked { - _end = 128 + _encodedLengths.atIndex(0); - } - _table.abiEncodedKeyNames = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - _table.abiEncodedFieldNames = (bytes(SliceLib.getSubslice(_blob, _start, _end).toBytes())); - } + function decode( + bytes memory _staticData, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (TablesData memory _table) { + (_table.fieldLayout, _table.keySchema, _table.valueSchema) = decodeStatic(_staticData); + + (_table.abiEncodedKeyNames, _table.abiEncodedFieldNames) = decodeDynamic(_encodedLengths, _dynamicData); } /** Tightly pack static data using this table's schema */ @@ -889,13 +919,13 @@ library Tables { bytes32 valueSchema, bytes memory abiEncodedKeyNames, bytes memory abiEncodedFieldNames - ) internal pure returns (bytes memory) { + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(fieldLayout, keySchema, valueSchema); PackedCounter _encodedLengths = encodeLengths(abiEncodedKeyNames, abiEncodedFieldNames); bytes memory _dynamicData = encodeDynamic(abiEncodedKeyNames, abiEncodedFieldNames); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/src/codegen/tables/Vector2.sol b/packages/store/src/codegen/tables/Vector2.sol index 707c7a3dac..58402ad8b5 100644 --- a/packages/store/src/codegen/tables/Vector2.sol +++ b/packages/store/src/codegen/tables/Vector2.sol @@ -188,8 +188,12 @@ library Vector2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -197,8 +201,12 @@ library Vector2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -206,8 +214,12 @@ library Vector2 { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -264,11 +276,26 @@ library Vector2 { set(_store, key, _table.x, _table.y); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (Vector2Data memory _table) { - _table.x = (uint32(Bytes.slice4(_blob, 0))); + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint32 x, uint32 y) { + x = (uint32(Bytes.slice4(_blob, 0))); - _table.y = (uint32(Bytes.slice4(_blob, 4))); + y = (uint32(Bytes.slice4(_blob, 4))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (Vector2Data memory _table) { + (_table.x, _table.y) = decodeStatic(_staticData); } /** Tightly pack static data using this table's schema */ @@ -277,13 +304,13 @@ library Vector2 { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 x, uint32 y) internal pure returns (bytes memory) { + function encode(uint32 x, uint32 y) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(x, y); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/store/test/Gas.t.sol b/packages/store/test/Gas.t.sol index bd776cd145..28233db4c6 100644 --- a/packages/store/test/Gas.t.sol +++ b/packages/store/test/Gas.t.sol @@ -52,11 +52,14 @@ contract GasTest is Test, GasReporter { endGasReport(); startGasReport("custom encode"); - bytes memory customEncoded = Mixed.encode(mixed.u32, mixed.u128, mixed.a32, mixed.s); + (bytes memory customEncodedStatic2, PackedCounter customEncodedLengths, bytes memory customEncodedDynamic2) = Mixed + .encode(mixed.u32, mixed.u128, mixed.a32, mixed.s); endGasReport(); + bytes memory customEncoded = abi.encodePacked(customEncodedStatic2, customEncodedLengths, customEncodedDynamic2); + startGasReport("custom decode"); - MixedData memory customDecoded = Mixed.decode(customEncoded); + MixedData memory customDecoded = Mixed.decode(customEncodedStatic2, customEncodedLengths, customEncodedDynamic2); endGasReport(); console.log("Length comparison: abi encode %s, custom %s", abiEncoded.length, customEncoded.length); diff --git a/packages/store/test/Mixed.t.sol b/packages/store/test/Mixed.t.sol index 453c405d4c..cf1d376d37 100644 --- a/packages/store/test/Mixed.t.sol +++ b/packages/store/test/Mixed.t.sol @@ -8,6 +8,7 @@ import { StoreCore } from "../src/StoreCore.sol"; import { StoreMock } from "../test/StoreMock.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { Schema } from "../src/Schema.sol"; +import { PackedCounter } from "../src/PackedCounter.sol"; contract MixedTest is Test, GasReporter, StoreMock { MixedData private testMixed; @@ -72,9 +73,9 @@ contract MixedTest is Test, GasReporter, StoreMock { a32[1] = 4; string memory s = "some string"; - assertEq( - Mixed.encode(1, 2, a32, s), - hex"0000000100000000000000000000000000000002000000000000000000000000000000000000000b0000000008000000000000130000000300000004736f6d6520737472696e67" - ); + (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) = Mixed.encode(1, 2, a32, s); + assertEq(staticData, hex"0000000100000000000000000000000000000002"); + assertEq(encodedLengths.unwrap(), hex"000000000000000000000000000000000000000b000000000800000000000013"); + assertEq(dynamicData, hex"0000000300000004736f6d6520737472696e67"); } } diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol index d9fa42b94d..32b03d19cb 100644 --- a/packages/store/test/StoreCore.t.sol +++ b/packages/store/test/StoreCore.t.sol @@ -235,9 +235,15 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data - bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); + (bytes memory loadedStaticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = IStore(this).getRecord( + tableId, + keyTuple, + fieldLayout + ); - assertTrue(Bytes.equals(staticData, loadedData)); + assertTrue(Bytes.equals(staticData, loadedStaticData)); + assertEq(_encodedLengths.unwrap(), bytes32(0)); + assertEq(_dynamicData, ""); } function testFailSetAndGetStaticData() public { @@ -287,9 +293,15 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data - bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); + (bytes memory loadedStaticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = IStore(this).getRecord( + tableId, + keyTuple, + fieldLayout + ); - assertTrue(Bytes.equals(staticData, loadedData)); + assertTrue(Bytes.equals(staticData, loadedStaticData)); + assertEq(_encodedLengths.unwrap(), bytes32(0)); + assertEq(_dynamicData, ""); } function testSetAndGetDynamicData() public { @@ -333,12 +345,6 @@ contract StoreCoreTest is Test, StoreMock { // Concat data bytes memory staticData = abi.encodePacked(firstDataBytes); bytes memory dynamicData = abi.encodePacked(secondDataBytes, thirdDataBytes); - bytes memory data = abi.encodePacked( - firstDataBytes, - encodedDynamicLength.unwrap(), - secondDataBytes, - thirdDataBytes - ); // Create keyTuple bytes32[] memory keyTuple = new bytes32[](1); @@ -352,10 +358,12 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); // Get data - bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); + (bytes memory loadedStaticData, PackedCounter loadedEncodedLengths, bytes memory loadedDynamicData) = IStore(this) + .getRecord(tableId, keyTuple, fieldLayout); - assertEq(loadedData.length, data.length); - assertEq(keccak256(loadedData), keccak256(data)); + assertEq(loadedStaticData, staticData); + assertEq(loadedEncodedLengths.unwrap(), encodedDynamicLength.unwrap()); + assertEq(loadedDynamicData, dynamicData); // Compare gas - setting the data as raw struct TestStruct memory _testStruct = TestStruct(0, new uint32[](2), new uint32[](3)); @@ -371,11 +379,23 @@ contract StoreCoreTest is Test, StoreMock { testMapping[1234] = abi.encode(_testStruct); } + struct SetAndGetData { + bytes32 tableId; + FieldLayout fieldLayout; + bytes16 firstDataBytes; + bytes firstDataPacked; + bytes32 secondDataBytes; + bytes secondDataPacked; + bytes thirdDataBytes; + bytes fourthDataBytes; + } + function testSetAndGetField() public { - bytes32 tableId = keccak256("some.tableId"); + SetAndGetData memory _data; + _data.tableId = keccak256("some.tableId"); // Register table - FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 2); + _data.fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 2); { Schema valueSchema = SchemaEncodeHelper.encode( SchemaType.UINT128, @@ -383,152 +403,180 @@ contract StoreCoreTest is Test, StoreMock { SchemaType.UINT32_ARRAY, SchemaType.UINT32_ARRAY ); - IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); + IStore(this).registerTable( + _data.tableId, + _data.fieldLayout, + defaultKeySchema, + valueSchema, + new string[](1), + new string[](4) + ); } - bytes16 firstDataBytes = bytes16(0x0102030405060708090a0b0c0d0e0f10); + _data.firstDataBytes = bytes16(0x0102030405060708090a0b0c0d0e0f10); // Create keyTuple bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = bytes32("some key"); - bytes memory firstDataPacked = abi.encodePacked(firstDataBytes); + _data.firstDataPacked = abi.encodePacked(_data.firstDataBytes); // Expect a StoreSpliceStaticData event to be emitted vm.expectEmit(true, true, true, true); - emit StoreSpliceStaticData(tableId, keyTuple, 0, uint40(firstDataPacked.length), firstDataPacked); + emit StoreSpliceStaticData(_data.tableId, keyTuple, 0, uint40(_data.firstDataPacked.length), _data.firstDataPacked); // Set first field - IStore(this).setField(tableId, keyTuple, 0, firstDataPacked, fieldLayout); + IStore(this).setField(_data.tableId, keyTuple, 0, _data.firstDataPacked, _data.fieldLayout); //////////////// // Static data //////////////// // Get first field - bytes memory loadedData = IStore(this).getField(tableId, keyTuple, 0, fieldLayout); + bytes memory loadedData = IStore(this).getField(_data.tableId, keyTuple, 0, _data.fieldLayout); // Verify loaded data is correct assertEq(loadedData.length, 16); - assertEq(bytes16(loadedData), bytes16(firstDataBytes)); + assertEq(bytes16(loadedData), bytes16(_data.firstDataBytes)); // Verify the second index is not set yet - assertEq(uint256(bytes32(IStore(this).getField(tableId, keyTuple, 1, fieldLayout))), 0); + assertEq(uint256(bytes32(IStore(this).getField(_data.tableId, keyTuple, 1, _data.fieldLayout))), 0); // Set second field - bytes32 secondDataBytes = keccak256("some data"); + _data.secondDataBytes = keccak256("some data"); - bytes memory secondDataPacked = abi.encodePacked(secondDataBytes); + _data.secondDataPacked = abi.encodePacked(_data.secondDataBytes); // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); emit StoreSpliceStaticData( - tableId, + _data.tableId, keyTuple, - uint48(firstDataPacked.length), - uint40(secondDataPacked.length), - secondDataPacked + uint48(_data.firstDataPacked.length), + uint40(_data.secondDataPacked.length), + _data.secondDataPacked ); - IStore(this).setField(tableId, keyTuple, 1, secondDataPacked, fieldLayout); + IStore(this).setField(_data.tableId, keyTuple, 1, _data.secondDataPacked, _data.fieldLayout); // Get second field - loadedData = IStore(this).getField(tableId, keyTuple, 1, fieldLayout); + loadedData = IStore(this).getField(_data.tableId, keyTuple, 1, _data.fieldLayout); // Verify loaded data is correct assertEq(loadedData.length, 32); - assertEq(bytes32(loadedData), secondDataBytes); + assertEq(bytes32(loadedData), _data.secondDataBytes); // Verify the first field didn't change - assertEq(bytes16(IStore(this).getField(tableId, keyTuple, 0, fieldLayout)), bytes16(firstDataBytes)); + assertEq( + bytes16(IStore(this).getField(_data.tableId, keyTuple, 0, _data.fieldLayout)), + bytes16(_data.firstDataBytes) + ); // Verify the full static data is correct - assertEq(IStore(this).getFieldLayout(tableId).staticDataLength(), 48); - assertEq(IStore(this).getValueSchema(tableId).staticDataLength(), 48); - assertEq(Bytes.slice16(IStore(this).getRecord(tableId, keyTuple, fieldLayout), 0), firstDataBytes); - assertEq(Bytes.slice32(IStore(this).getRecord(tableId, keyTuple, fieldLayout), 16), secondDataBytes); + (bytes memory loadedStaticData, PackedCounter loadedEncodedLengths, bytes memory loadedDynamicData) = IStore(this) + .getRecord(_data.tableId, keyTuple, _data.fieldLayout); + assertEq(IStore(this).getFieldLayout(_data.tableId).staticDataLength(), 48); + assertEq(IStore(this).getValueSchema(_data.tableId).staticDataLength(), 48); + assertEq(Bytes.slice16(loadedStaticData, 0), _data.firstDataBytes); + assertEq(Bytes.slice32(loadedStaticData, 16), _data.secondDataBytes); assertEq( - keccak256(SliceLib.getSubslice(IStore(this).getRecord(tableId, keyTuple, fieldLayout), 0, 48).toBytes()), - keccak256(abi.encodePacked(firstDataBytes, secondDataBytes)) + keccak256(SliceLib.getSubslice(loadedStaticData, 0, 48).toBytes()), + keccak256(abi.encodePacked(_data.firstDataBytes, _data.secondDataBytes)) ); //////////////// // Dynamic data //////////////// - bytes memory thirdDataBytes; { uint32[] memory thirdData = new uint32[](2); thirdData[0] = 0x11121314; thirdData[1] = 0x15161718; - thirdDataBytes = EncodeArray.encode(thirdData); + _data.thirdDataBytes = EncodeArray.encode(thirdData); } - bytes memory fourthDataBytes; { uint32[] memory fourthData = new uint32[](3); fourthData[0] = 0x191a1b1c; fourthData[1] = 0x1d1e1f20; fourthData[2] = 0x21222324; - fourthDataBytes = EncodeArray.encode(fourthData); + _data.fourthDataBytes = EncodeArray.encode(fourthData); } // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); emit StoreSpliceDynamicData( - tableId, + _data.tableId, keyTuple, uint48(0), 0, - thirdDataBytes, - PackedCounterLib.pack(thirdDataBytes.length, 0).unwrap() + _data.thirdDataBytes, + PackedCounterLib.pack(_data.thirdDataBytes.length, 0).unwrap() ); // Set third field - IStore(this).setField(tableId, keyTuple, 2, thirdDataBytes, fieldLayout); + IStore(this).setField(_data.tableId, keyTuple, 2, _data.thirdDataBytes, _data.fieldLayout); // Get third field - loadedData = IStore(this).getField(tableId, keyTuple, 2, fieldLayout); + loadedData = IStore(this).getField(_data.tableId, keyTuple, 2, _data.fieldLayout); // Verify loaded data is correct assertEq(SliceLib.fromBytes(loadedData).decodeArray_uint32().length, 2); - assertEq(loadedData.length, thirdDataBytes.length); - assertEq(keccak256(loadedData), keccak256(thirdDataBytes)); + assertEq(loadedData.length, _data.thirdDataBytes.length); + assertEq(keccak256(loadedData), keccak256(_data.thirdDataBytes)); // Verify the fourth field is not set yet - assertEq(IStore(this).getField(tableId, keyTuple, 3, fieldLayout).length, 0); + assertEq(IStore(this).getField(_data.tableId, keyTuple, 3, _data.fieldLayout).length, 0); // Verify none of the previous fields were impacted - assertEq(bytes16(IStore(this).getField(tableId, keyTuple, 0, fieldLayout)), bytes16(firstDataBytes)); - assertEq(bytes32(IStore(this).getField(tableId, keyTuple, 1, fieldLayout)), bytes32(secondDataBytes)); + assertEq( + bytes16(IStore(this).getField(_data.tableId, keyTuple, 0, _data.fieldLayout)), + bytes16(_data.firstDataBytes) + ); + assertEq( + bytes32(IStore(this).getField(_data.tableId, keyTuple, 1, _data.fieldLayout)), + bytes32(_data.secondDataBytes) + ); // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); emit StoreSpliceDynamicData( - tableId, + _data.tableId, keyTuple, - uint48(thirdDataBytes.length), + uint48(_data.thirdDataBytes.length), 0, - fourthDataBytes, - PackedCounterLib.pack(thirdDataBytes.length, fourthDataBytes.length).unwrap() + _data.fourthDataBytes, + PackedCounterLib.pack(_data.thirdDataBytes.length, _data.fourthDataBytes.length).unwrap() ); // Set fourth field - IStore(this).setField(tableId, keyTuple, 3, fourthDataBytes, fieldLayout); + IStore(this).setField(_data.tableId, keyTuple, 3, _data.fourthDataBytes, _data.fieldLayout); // Get fourth field - loadedData = IStore(this).getField(tableId, keyTuple, 3, fieldLayout); + loadedData = IStore(this).getField(_data.tableId, keyTuple, 3, _data.fieldLayout); // Verify loaded data is correct - assertEq(loadedData.length, fourthDataBytes.length); - assertEq(keccak256(loadedData), keccak256(fourthDataBytes)); + assertEq(loadedData.length, _data.fourthDataBytes.length); + assertEq(keccak256(loadedData), keccak256(_data.fourthDataBytes)); // Verify all fields are correct - PackedCounter encodedLengths = PackedCounterLib.pack(uint40(thirdDataBytes.length), uint40(fourthDataBytes.length)); + PackedCounter encodedLengths = PackedCounterLib.pack( + uint40(_data.thirdDataBytes.length), + uint40(_data.fourthDataBytes.length) + ); + (loadedStaticData, loadedEncodedLengths, loadedDynamicData) = IStore(this).getRecord( + _data.tableId, + keyTuple, + _data.fieldLayout + ); assertEq( - keccak256(IStore(this).getRecord(tableId, keyTuple, fieldLayout)), - keccak256( - abi.encodePacked(firstDataBytes, secondDataBytes, encodedLengths.unwrap(), thirdDataBytes, fourthDataBytes) + abi.encodePacked(loadedStaticData, loadedEncodedLengths, loadedDynamicData), + abi.encodePacked( + _data.firstDataBytes, + _data.secondDataBytes, + encodedLengths.unwrap(), + _data.thirdDataBytes, + _data.fourthDataBytes ) ); @@ -538,23 +586,23 @@ contract StoreCoreTest is Test, StoreMock { // Expect a StoreSpliceRecord event to be emitted vm.expectEmit(true, true, true, true); emit StoreSpliceDynamicData( - tableId, + _data.tableId, keyTuple, - uint48(thirdDataBytes.length), - uint40(fourthDataBytes.length), - thirdDataBytes, - PackedCounterLib.pack(thirdDataBytes.length, thirdDataBytes.length).unwrap() + uint48(_data.thirdDataBytes.length), + uint40(_data.fourthDataBytes.length), + _data.thirdDataBytes, + PackedCounterLib.pack(_data.thirdDataBytes.length, _data.thirdDataBytes.length).unwrap() ); // Set fourth field - IStore(this).setField(tableId, keyTuple, 3, thirdDataBytes, fieldLayout); + IStore(this).setField(_data.tableId, keyTuple, 3, _data.thirdDataBytes, _data.fieldLayout); // Get fourth field - loadedData = IStore(this).getField(tableId, keyTuple, 3, fieldLayout); + loadedData = IStore(this).getField(_data.tableId, keyTuple, 3, _data.fieldLayout); // Verify loaded data is correct - assertEq(loadedData.length, thirdDataBytes.length); - assertEq(keccak256(loadedData), keccak256(thirdDataBytes)); + assertEq(loadedData.length, _data.thirdDataBytes.length); + assertEq(keccak256(loadedData), keccak256(_data.thirdDataBytes)); } function testDeleteData() public { @@ -613,10 +661,10 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setRecord(tableId, keyTuple, staticData, encodedDynamicLength, dynamicData, fieldLayout); // Get data - bytes memory loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); + (bytes memory loadedStaticData, PackedCounter loadedEncodedLengths, bytes memory loadedDynamicData) = IStore(this) + .getRecord(tableId, keyTuple, fieldLayout); - assertEq(loadedData.length, data.length); - assertEq(keccak256(loadedData), keccak256(data)); + assertEq(abi.encodePacked(loadedStaticData, loadedEncodedLengths, loadedDynamicData), data); // Expect a StoreDeleteRecord event to be emitted vm.expectEmit(true, true, true, true); @@ -626,8 +674,14 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).deleteRecord(tableId, keyTuple, fieldLayout); // Verify data is deleted - loadedData = IStore(this).getRecord(tableId, keyTuple, fieldLayout); - assertEq(keccak256(loadedData), keccak256(new bytes(fieldLayout.staticDataLength()))); + (loadedStaticData, loadedEncodedLengths, loadedDynamicData) = IStore(this).getRecord( + tableId, + keyTuple, + fieldLayout + ); + assertEq(loadedStaticData, new bytes(fieldLayout.staticDataLength())); + assertEq(loadedEncodedLengths.unwrap(), bytes32(0)); + assertEq(loadedDynamicData, ""); } struct TestPushToFieldData { @@ -941,7 +995,7 @@ contract StoreCoreTest is Test, StoreMock { bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = bytes32("some key"); - bytes memory data1 = IStore(this).getRecord(tableId, keyTuple, fieldLayout); + (bytes memory data1, , ) = IStore(this).getRecord(tableId, keyTuple, fieldLayout); assertEq(data1.length, fieldLayout.staticDataLength()); bytes memory data2 = IStore(this).getField(tableId, keyTuple, 0, fieldLayout); @@ -984,7 +1038,7 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there - bytes memory indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); + (bytes memory indexedData, , ) = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); assertEq(keccak256(staticData), keccak256(indexedData)); staticData = abi.encodePacked(bytes16(0x1112131415161718191a1b1c1d1e1f20)); @@ -992,13 +1046,13 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).setField(tableId, keyTuple, 0, staticData, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there - indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); + (indexedData, , ) = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); assertEq(keccak256(staticData), keccak256(indexedData)); IStore(this).deleteRecord(tableId, keyTuple, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there - indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); + (indexedData, , ) = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); assertEq(keccak256(indexedData), keccak256(abi.encodePacked(bytes16(0)))); } @@ -1087,6 +1141,12 @@ contract StoreCoreTest is Test, StoreMock { IStore(this).deleteRecord(tableId, keyTuple, fieldLayout); } + struct RecordData { + bytes staticData; + PackedCounter encodedLengths; + bytes dynamicData; + } + function testHooksDynamicData() public { bytes32 tableId = keccak256("some.tableId"); bytes32[] memory keyTuple = new bytes32[](1); @@ -1112,33 +1172,59 @@ contract StoreCoreTest is Test, StoreMock { uint32[] memory arrayData = new uint32[](1); arrayData[0] = 0x01020304; bytes memory arrayDataBytes = EncodeArray.encode(arrayData); - PackedCounter encodedArrayDataLength = PackedCounterLib.pack(uint40(arrayDataBytes.length)); - bytes memory dynamicData = arrayDataBytes; - bytes memory staticData = abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)); - bytes memory data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); + RecordData memory recordData = RecordData({ + staticData: abi.encodePacked(bytes16(0x0102030405060708090a0b0c0d0e0f10)), + encodedLengths: PackedCounterLib.pack(uint40(arrayDataBytes.length)), + dynamicData: arrayDataBytes + }); - IStore(this).setRecord(tableId, keyTuple, staticData, encodedArrayDataLength, dynamicData, fieldLayout); + IStore(this).setRecord( + tableId, + keyTuple, + recordData.staticData, + recordData.encodedLengths, + recordData.dynamicData, + fieldLayout + ); // Get data from indexed table - the indexer should have mirrored the data there - bytes memory indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); - assertEq(keccak256(data), keccak256(indexedData)); + RecordData memory loadedData; + (loadedData.staticData, loadedData.encodedLengths, loadedData.dynamicData) = IStore(this).getRecord( + indexerTableId, + keyTuple, + fieldLayout + ); + assertEq(loadedData.staticData, recordData.staticData); + assertEq(loadedData.encodedLengths.unwrap(), recordData.encodedLengths.unwrap()); + assertEq(loadedData.dynamicData, recordData.dynamicData); // Update dynamic data arrayData[0] = 0x11121314; arrayDataBytes = EncodeArray.encode(arrayData); - dynamicData = arrayDataBytes; - data = abi.encodePacked(staticData, encodedArrayDataLength, dynamicData); + recordData.dynamicData = arrayDataBytes; IStore(this).setField(tableId, keyTuple, 1, arrayDataBytes, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there - indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); - assertEq(keccak256(data), keccak256(indexedData)); + (loadedData.staticData, loadedData.encodedLengths, loadedData.dynamicData) = IStore(this).getRecord( + indexerTableId, + keyTuple, + fieldLayout + ); + assertEq(loadedData.staticData, recordData.staticData); + assertEq(loadedData.encodedLengths.unwrap(), recordData.encodedLengths.unwrap()); + assertEq(loadedData.dynamicData, recordData.dynamicData); IStore(this).deleteRecord(tableId, keyTuple, fieldLayout); // Get data from indexed table - the indexer should have mirrored the data there - indexedData = IStore(this).getRecord(indexerTableId, keyTuple, fieldLayout); - assertEq(keccak256(indexedData), keccak256(abi.encodePacked(bytes16(0)))); + (loadedData.staticData, loadedData.encodedLengths, loadedData.dynamicData) = IStore(this).getRecord( + indexerTableId, + keyTuple, + fieldLayout + ); + assertEq(loadedData.staticData, abi.encodePacked(bytes16(0))); + assertEq(loadedData.encodedLengths.unwrap(), bytes32(0)); + assertEq(loadedData.dynamicData, ""); } } diff --git a/packages/store/ts/codegen/record.ts b/packages/store/ts/codegen/record.ts index 9b8b99bb99..7f1636ce62 100644 --- a/packages/store/ts/codegen/record.ts +++ b/packages/store/ts/codegen/record.ts @@ -22,8 +22,13 @@ export function renderRecordMethods(options: RenderTableOptions) { _typedKeyArgs, ])}) internal view returns (${renderDecodedRecord(options)}) { ${_keyTupleDefinition} - bytes memory _blob = ${_store}.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + + ( + bytes memory _staticData, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) = ${_store}.getRecord(_tableId, _keyTuple, _fieldLayout); + return decode(_staticData, _encodedLengths, _dynamicData); } ` ); @@ -69,7 +74,7 @@ export function renderRecordMethods(options: RenderTableOptions) { ); } - result += renderDecodeFunction(options); + result += renderDecodeFunctions(options); return result; } @@ -78,7 +83,7 @@ export function renderRecordData(options: RenderTableOptions) { let result = ""; if (options.staticFields.length > 0) { result += ` - bytes memory _staticData = encodeStatic(${renderArguments(options.staticFields.map(({ name }) => name))}); + bytes memory _staticData = encodeStatic(${renderArguments(options.staticFields.map(({ name }) => name))}); `; } else { result += `bytes memory _staticData;`; @@ -86,13 +91,13 @@ export function renderRecordData(options: RenderTableOptions) { if (options.dynamicFields.length > 0) { result += ` - PackedCounter _encodedLengths = encodeLengths(${renderArguments(options.dynamicFields.map(({ name }) => name))}); - bytes memory _dynamicData = encodeDynamic(${renderArguments(options.dynamicFields.map(({ name }) => name))}); + PackedCounter _encodedLengths = encodeLengths(${renderArguments(options.dynamicFields.map(({ name }) => name))}); + bytes memory _dynamicData = encodeDynamic(${renderArguments(options.dynamicFields.map(({ name }) => name))}); `; } else { result += ` - PackedCounter _encodedLengths; - bytes memory _dynamicData; + PackedCounter _encodedLengths; + bytes memory _dynamicData; `; } @@ -100,7 +105,7 @@ export function renderRecordData(options: RenderTableOptions) { } // Renders the `decode` function that parses a bytes blob into the table data -function renderDecodeFunction({ structName, fields, staticFields, dynamicFields }: RenderTableOptions) { +function renderDecodeFunctions({ structName, fields, staticFields, dynamicFields }: RenderTableOptions) { // either set struct properties, or just variables const renderedDecodedRecord = structName ? `${structName} memory _table` @@ -115,69 +120,95 @@ function renderDecodeFunction({ structName, fields, staticFields, dynamicFields _acc += field.staticByteLength; } - if (dynamicFields.length > 0) { - const totalStaticLength = staticFields.reduce((acc, { staticByteLength }) => acc + staticByteLength, 0); - // decode static (optionally) and dynamic data - return ` + let result = ""; + + if (staticFields.length > 0) { + result += ` /** - * Decode the tightly packed blob using this table's field layout. - * Undefined behaviour for invalid blobs. + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs */ - function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { - // ${totalStaticLength} is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, ${totalStaticLength})); - + function decodeStatic(bytes memory _blob) internal pure returns (${renderArguments( + staticFields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) + )}) { ${renderList( staticFields, (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; + ${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; ` )} - // Store trims the blob if dynamic fields are all empty - if (_blob.length > ${totalStaticLength}) { - ${renderList( - dynamicFields, - // unchecked is only dangerous if _encodedLengths (and _blob) is invalid, - // but it's assumed to be valid, and this function is meant to be mostly used internally - (field, index) => { - if (index === 0) { - return ` - // skip static data length + dynamic lengths word - uint256 _start = ${totalStaticLength + 32}; - uint256 _end; - unchecked { - _end = ${totalStaticLength + 32} + _encodedLengths.atIndex(${index}); - } - ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; - `; - } else { - return ` - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(${index}); - } - ${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)}; - `; - } - } - )} - } } `; - } else { - // decode only static data - return ` - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) { + } + + if (dynamicFields.length > 0) { + result += ` + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic(PackedCounter _encodedLengths, bytes memory _blob) internal pure returns (${renderArguments( + dynamicFields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) + )}) { ${renderList( - staticFields, - (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; - ` + dynamicFields, + // unchecked is only dangerous if _encodedLengths (and _blob) is invalid, + // but it's assumed to be valid, and this function is meant to be mostly used internally + (field, index) => { + if (index === 0) { + return ` + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(${index}); + } + ${field.name} = ${renderDecodeDynamicFieldPartial(field)}; + `; + } else { + return ` + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(${index}); + } + ${field.name} = ${renderDecodeDynamicFieldPartial(field)}; + `; + } + } )} } `; } + + result += ` + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory ${staticFields.length > 0 ? "_staticData" : ""}, + PackedCounter ${dynamicFields.length > 0 ? "_encodedLengths" : ""}, + bytes memory ${dynamicFields.length > 0 ? "_dynamicData" : ""} + ) internal pure returns (${renderedDecodedRecord}) { + `; + + if (staticFields.length > 0) { + result += ` + (${renderArguments(staticFields.map((field) => `${fieldNamePrefix}${field.name}`))}) = decodeStatic(_staticData); + `; + } + if (dynamicFields.length > 0) { + result += ` + (${renderArguments( + dynamicFields.map((field) => `${fieldNamePrefix}${field.name}`) + )}) = decodeDynamic(_encodedLengths, _dynamicData); + `; + } + + result += ` + } + `; + + return result; } // contents of `returns (...)` for record getter/decoder diff --git a/packages/store/ts/codegen/renderTable.ts b/packages/store/ts/codegen/renderTable.ts index 0d4c8e4b48..ad0b6d9cab 100644 --- a/packages/store/ts/codegen/renderTable.ts +++ b/packages/store/ts/codegen/renderTable.ts @@ -136,10 +136,10 @@ export function renderTable(options: RenderTableOptions) { /** Tightly pack full data using this table's field layout */ function encode(${renderArguments( options.fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`) - )}) internal pure returns (bytes memory) { + )}) internal pure returns (bytes memory, PackedCounter, bytes memory) { ${renderRecordData(options)} - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 24a569c163..f4ba05a6c7 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -39,13 +39,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1415019 + "gasUsed": 1415024 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1415019 + "gasUsed": 1415024 }, { "file": "test/KeysInTableModule.t.sol", @@ -57,13 +57,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1415019 + "gasUsed": 1415024 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1415019 + "gasUsed": 1415024 }, { "file": "test/KeysInTableModule.t.sol", @@ -75,13 +75,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 171464 + "gasUsed": 172418 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1415019 + "gasUsed": 1415024 }, { "file": "test/KeysInTableModule.t.sol", @@ -93,13 +93,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 88115 + "gasUsed": 88978 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 654303 + "gasUsed": 654245 }, { "file": "test/KeysWithValueModule.t.sol", @@ -117,49 +117,49 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 654303 + "gasUsed": 654245 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 134417 + "gasUsed": 135351 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 654303 + "gasUsed": 654245 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 103992 + "gasUsed": 104926 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 32871 + "gasUsed": 33805 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 654303 + "gasUsed": 654245 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 139031 + "gasUsed": 140853 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 103790 + "gasUsed": 105611 }, { "file": "test/query.t.sol", @@ -231,67 +231,67 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 114074 + "gasUsed": 113953 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "call a system via a callbound delegation", - "gasUsed": 33580 + "gasUsed": 33584 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 108511 + "gasUsed": 108390 }, { "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "call a system via a timebound delegation", - "gasUsed": 26692 + "gasUsed": 26696 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 678987 + "gasUsed": 678930 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 51179 + "gasUsed": 51180 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 669046 + "gasUsed": 668988 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 51179 + "gasUsed": 51180 }, { "file": "test/World.t.sol", "test": "testCall", "name": "call a system via the World", - "gasUsed": 12333 + "gasUsed": 12380 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 50259 + "gasUsed": 50260 }, { "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "call a system via an unlimited delegation", - "gasUsed": 12682 + "gasUsed": 12729 }, { "file": "test/World.t.sol", @@ -309,37 +309,37 @@ "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 58888 + "gasUsed": 58889 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 52205 + "gasUsed": 52206 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 79482 + "gasUsed": 79483 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 122752 + "gasUsed": 122753 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 74123 + "gasUsed": 74124 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 641673 + "gasUsed": 641613 }, { "file": "test/World.t.sol", diff --git a/packages/world/src/modules/core/tables/Balances.sol b/packages/world/src/modules/core/tables/Balances.sol index e87221fb49..05e64fc99f 100644 --- a/packages/world/src/modules/core/tables/Balances.sol +++ b/packages/world/src/modules/core/tables/Balances.sol @@ -131,13 +131,13 @@ library Balances { } /** Tightly pack full data using this table's field layout */ - function encode(uint256 balance) internal pure returns (bytes memory) { + function encode(uint256 balance) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(balance); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/FunctionSelectors.sol b/packages/world/src/modules/core/tables/FunctionSelectors.sol index 036335865a..e92a0ff17b 100644 --- a/packages/world/src/modules/core/tables/FunctionSelectors.sol +++ b/packages/world/src/modules/core/tables/FunctionSelectors.sol @@ -191,8 +191,12 @@ library FunctionSelectors { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(functionSelector); - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -202,8 +206,12 @@ library FunctionSelectors { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(functionSelector); - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -214,8 +222,12 @@ library FunctionSelectors { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = bytes32(functionSelector); - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -262,26 +274,46 @@ library FunctionSelectors { _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (bytes32 resourceSelector, bytes4 systemFunctionSelector) { + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic( + bytes memory _blob + ) internal pure returns (bytes32 resourceSelector, bytes4 systemFunctionSelector) { resourceSelector = (Bytes.slice32(_blob, 0)); systemFunctionSelector = (Bytes.slice4(_blob, 32)); } + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (bytes32 resourceSelector, bytes4 systemFunctionSelector) { + (resourceSelector, systemFunctionSelector) = decodeStatic(_staticData); + } + /** Tightly pack static data using this table's schema */ function encodeStatic(bytes32 resourceSelector, bytes4 systemFunctionSelector) internal pure returns (bytes memory) { return abi.encodePacked(resourceSelector, systemFunctionSelector); } /** Tightly pack full data using this table's field layout */ - function encode(bytes32 resourceSelector, bytes4 systemFunctionSelector) internal pure returns (bytes memory) { + function encode( + bytes32 resourceSelector, + bytes4 systemFunctionSelector + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(resourceSelector, systemFunctionSelector); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/ResourceType.sol b/packages/world/src/modules/core/tables/ResourceType.sol index 141d9b1bb5..ac9983441a 100644 --- a/packages/world/src/modules/core/tables/ResourceType.sol +++ b/packages/world/src/modules/core/tables/ResourceType.sol @@ -134,13 +134,13 @@ library ResourceType { } /** Tightly pack full data using this table's field layout */ - function encode(Resource resourceType) internal pure returns (bytes memory) { + function encode(Resource resourceType) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(resourceType); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/SystemHooks.sol b/packages/world/src/modules/core/tables/SystemHooks.sol index a322e22ced..57bf414ebd 100644 --- a/packages/world/src/modules/core/tables/SystemHooks.sol +++ b/packages/world/src/modules/core/tables/SystemHooks.sol @@ -315,12 +315,12 @@ library SystemHooks { } /** Tightly pack full data using this table's field layout */ - function encode(bytes21[] memory value) internal pure returns (bytes memory) { + function encode(bytes21[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/SystemRegistry.sol b/packages/world/src/modules/core/tables/SystemRegistry.sol index 42f7311644..71174911aa 100644 --- a/packages/world/src/modules/core/tables/SystemRegistry.sol +++ b/packages/world/src/modules/core/tables/SystemRegistry.sol @@ -131,13 +131,13 @@ library SystemRegistry { } /** Tightly pack full data using this table's field layout */ - function encode(bytes32 resourceSelector) internal pure returns (bytes memory) { + function encode(bytes32 resourceSelector) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(resourceSelector); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/core/tables/Systems.sol b/packages/world/src/modules/core/tables/Systems.sol index f60421fce5..8a60905326 100644 --- a/packages/world/src/modules/core/tables/Systems.sol +++ b/packages/world/src/modules/core/tables/Systems.sol @@ -183,8 +183,12 @@ library Systems { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = resourceSelector; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -192,8 +196,12 @@ library Systems { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = resourceSelector; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -201,8 +209,12 @@ library Systems { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = resourceSelector; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -244,26 +256,41 @@ library Systems { _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (address system, bool publicAccess) { + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (address system, bool publicAccess) { system = (address(Bytes.slice20(_blob, 0))); publicAccess = (_toBool(uint8(Bytes.slice1(_blob, 20)))); } + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (address system, bool publicAccess) { + (system, publicAccess) = decodeStatic(_staticData); + } + /** Tightly pack static data using this table's schema */ function encodeStatic(address system, bool publicAccess) internal pure returns (bytes memory) { return abi.encodePacked(system, publicAccess); } /** Tightly pack full data using this table's field layout */ - function encode(address system, bool publicAccess) internal pure returns (bytes memory) { + function encode(address system, bool publicAccess) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(system, publicAccess); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/keysintable/tables/KeysInTable.sol b/packages/world/src/modules/keysintable/tables/KeysInTable.sol index 5ba1921b92..7de0288cc7 100644 --- a/packages/world/src/modules/keysintable/tables/KeysInTable.sol +++ b/packages/world/src/modules/keysintable/tables/KeysInTable.sol @@ -1230,8 +1230,12 @@ library KeysInTable { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = sourceTable; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -1239,8 +1243,12 @@ library KeysInTable { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = sourceTable; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -1248,8 +1256,12 @@ library KeysInTable { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = sourceTable; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -1325,48 +1337,69 @@ library KeysInTable { set(_store, sourceTable, _table.keys0, _table.keys1, _table.keys2, _table.keys3, _table.keys4); } + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeDynamic( + PackedCounter _encodedLengths, + bytes memory _blob + ) + internal + pure + returns ( + bytes32[] memory keys0, + bytes32[] memory keys1, + bytes32[] memory keys2, + bytes32[] memory keys3, + bytes32[] memory keys4 + ) + { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + keys0 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(1); + } + keys1 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(2); + } + keys2 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(3); + } + keys3 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + + _start = _end; + unchecked { + _end += _encodedLengths.atIndex(4); + } + keys4 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); + } + /** * Decode the tightly packed blob using this table's field layout. * Undefined behaviour for invalid blobs. */ - function decode(bytes memory _blob) internal pure returns (KeysInTableData memory _table) { - // 0 is the total byte length of static data - PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, 0)); - - // Store trims the blob if dynamic fields are all empty - if (_blob.length > 0) { - // skip static data length + dynamic lengths word - uint256 _start = 32; - uint256 _end; - unchecked { - _end = 32 + _encodedLengths.atIndex(0); - } - _table.keys0 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(1); - } - _table.keys1 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(2); - } - _table.keys2 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(3); - } - _table.keys3 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - - _start = _end; - unchecked { - _end += _encodedLengths.atIndex(4); - } - _table.keys4 = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_bytes32()); - } + function decode( + bytes memory, + PackedCounter _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (KeysInTableData memory _table) { + (_table.keys0, _table.keys1, _table.keys2, _table.keys3, _table.keys4) = decodeDynamic( + _encodedLengths, + _dynamicData + ); } /** Tightly pack dynamic data using this table's schema */ @@ -1414,12 +1447,12 @@ library KeysInTable { bytes32[] memory keys2, bytes32[] memory keys3, bytes32[] memory keys4 - ) internal pure returns (bytes memory) { + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(keys0, keys1, keys2, keys3, keys4); bytes memory _dynamicData = encodeDynamic(keys0, keys1, keys2, keys3, keys4); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol b/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol index c50753a03b..f674c7aadc 100644 --- a/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol +++ b/packages/world/src/modules/keysintable/tables/UsedKeysIndex.sol @@ -198,8 +198,12 @@ library UsedKeysIndex { _keyTuple[0] = sourceTable; _keyTuple[1] = keysHash; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -208,8 +212,12 @@ library UsedKeysIndex { _keyTuple[0] = sourceTable; _keyTuple[1] = keysHash; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -218,8 +226,12 @@ library UsedKeysIndex { _keyTuple[0] = sourceTable; _keyTuple[1] = keysHash; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -264,26 +276,41 @@ library UsedKeysIndex { _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (bool has, uint40 index) { + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (bool has, uint40 index) { has = (_toBool(uint8(Bytes.slice1(_blob, 0)))); index = (uint40(Bytes.slice5(_blob, 1))); } + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (bool has, uint40 index) { + (has, index) = decodeStatic(_staticData); + } + /** Tightly pack static data using this table's schema */ function encodeStatic(bool has, uint40 index) internal pure returns (bytes memory) { return abi.encodePacked(has, index); } /** Tightly pack full data using this table's field layout */ - function encode(bool has, uint40 index) internal pure returns (bytes memory) { + function encode(bool has, uint40 index) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(has, index); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol index 3c3c822c0f..a16a09208e 100644 --- a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol +++ b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol @@ -41,7 +41,7 @@ contract KeysWithValueHook is StoreHook { bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); // Get the previous value - bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, keyTuple, fieldLayout)); + bytes32 previousValue = _getRecordValue(sourceTableId, keyTuple, fieldLayout); // Remove the key from the list of keys with the previous value _removeKeyFromList(targetTableId, keyTuple[0], previousValue); @@ -75,7 +75,7 @@ contract KeysWithValueHook is StoreHook { FieldLayout fieldLayout ) public { // Remove the key from the list of keys with the previous value - bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, keyTuple, fieldLayout)); + bytes32 previousValue = _getRecordValue(sourceTableId, keyTuple, fieldLayout); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); _removeKeyFromList(targetTableId, keyTuple[0], previousValue); } @@ -88,14 +88,14 @@ contract KeysWithValueHook is StoreHook { FieldLayout fieldLayout ) public { // Add the key to the list of keys with the new value - bytes32 newValue = keccak256(_world().getRecord(sourceTableId, keyTuple, fieldLayout)); + bytes32 newValue = _getRecordValue(sourceTableId, keyTuple, fieldLayout); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); KeysWithValue.push(targetTableId, newValue, keyTuple[0]); } function onBeforeDeleteRecord(bytes32 sourceTableId, bytes32[] memory keyTuple, FieldLayout fieldLayout) public { // Remove the key from the list of keys with the previous value - bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, keyTuple, fieldLayout)); + bytes32 previousValue = _getRecordValue(sourceTableId, keyTuple, fieldLayout); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); _removeKeyFromList(targetTableId, keyTuple[0], previousValue); } @@ -104,6 +104,23 @@ contract KeysWithValueHook is StoreHook { // NOOP } + function _getRecordValue( + bytes32 sourceTableId, + bytes32[] memory keyTuple, + FieldLayout fieldLayout + ) internal view returns (bytes32 previousValue) { + (bytes memory staticData, PackedCounter encodedLengths, bytes memory dynamicData) = _world().getRecord( + sourceTableId, + keyTuple, + fieldLayout + ); + if (dynamicData.length > 0) { + return keccak256(abi.encodePacked(staticData, encodedLengths, dynamicData)); + } else { + return keccak256(staticData); + } + } + function _removeKeyFromList(bytes32 targetTableId, bytes32 key, bytes32 valueHash) internal { // Get the keys with the previous value excluding the current key bytes32[] memory keysWithPreviousValue = KeysWithValue.get(targetTableId, valueHash).filter(key); diff --git a/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol b/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol index 76db05f36f..a49c3cc511 100644 --- a/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol +++ b/packages/world/src/modules/keyswithvalue/tables/KeysWithValue.sol @@ -316,12 +316,12 @@ library KeysWithValue { } /** Tightly pack full data using this table's field layout */ - function encode(bytes32[] memory keysWithValue) internal pure returns (bytes memory) { + function encode(bytes32[] memory keysWithValue) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(keysWithValue); bytes memory _dynamicData = encodeDynamic(keysWithValue); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol b/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol index 69367d5c30..e0dae09e55 100644 --- a/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol +++ b/packages/world/src/modules/std-delegations/tables/CallboundDelegations.sol @@ -190,13 +190,13 @@ library CallboundDelegations { } /** Tightly pack full data using this table's field layout */ - function encode(uint256 availableCalls) internal pure returns (bytes memory) { + function encode(uint256 availableCalls) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(availableCalls); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol b/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol index 30a11520f3..17cbd3702b 100644 --- a/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol +++ b/packages/world/src/modules/std-delegations/tables/TimeboundDelegations.sol @@ -139,13 +139,13 @@ library TimeboundDelegations { } /** Tightly pack full data using this table's field layout */ - function encode(uint256 maxTimestamp) internal pure returns (bytes memory) { + function encode(uint256 maxTimestamp) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(maxTimestamp); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol b/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol index 7c0fc191e7..240d9abe37 100644 --- a/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol +++ b/packages/world/src/modules/uniqueentity/tables/UniqueEntity.sol @@ -120,13 +120,13 @@ library UniqueEntity { } /** Tightly pack full data using this table's field layout */ - function encode(uint256 value) internal pure returns (bytes memory) { + function encode(uint256 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/Delegations.sol b/packages/world/src/tables/Delegations.sol index 4226caa189..5b592ab79e 100644 --- a/packages/world/src/tables/Delegations.sol +++ b/packages/world/src/tables/Delegations.sol @@ -143,13 +143,13 @@ library Delegations { } /** Tightly pack full data using this table's field layout */ - function encode(bytes32 delegationControlId) internal pure returns (bytes memory) { + function encode(bytes32 delegationControlId) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(delegationControlId); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/InstalledModules.sol b/packages/world/src/tables/InstalledModules.sol index 530489f7a8..541f7bc36c 100644 --- a/packages/world/src/tables/InstalledModules.sol +++ b/packages/world/src/tables/InstalledModules.sol @@ -139,13 +139,13 @@ library InstalledModules { } /** Tightly pack full data using this table's field layout */ - function encode(address moduleAddress) internal pure returns (bytes memory) { + function encode(address moduleAddress) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(moduleAddress); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/NamespaceOwner.sol b/packages/world/src/tables/NamespaceOwner.sol index 5b7946f7a9..4c5977fb41 100644 --- a/packages/world/src/tables/NamespaceOwner.sol +++ b/packages/world/src/tables/NamespaceOwner.sol @@ -131,13 +131,13 @@ library NamespaceOwner { } /** Tightly pack full data using this table's field layout */ - function encode(address owner) internal pure returns (bytes memory) { + function encode(address owner) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(owner); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/src/tables/ResourceAccess.sol b/packages/world/src/tables/ResourceAccess.sol index 9d591d49ef..553447e59f 100644 --- a/packages/world/src/tables/ResourceAccess.sol +++ b/packages/world/src/tables/ResourceAccess.sol @@ -139,13 +139,13 @@ library ResourceAccess { } /** Tightly pack full data using this table's field layout */ - function encode(bool access) internal pure returns (bytes memory) { + function encode(bool access) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(access); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/test/tables/AddressArray.sol b/packages/world/test/tables/AddressArray.sol index e2b29648b8..b5bed3519a 100644 --- a/packages/world/test/tables/AddressArray.sol +++ b/packages/world/test/tables/AddressArray.sol @@ -312,12 +312,12 @@ library AddressArray { } /** Tightly pack full data using this table's field layout */ - function encode(address[] memory value) internal pure returns (bytes memory) { + function encode(address[] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData; PackedCounter _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/packages/world/test/tables/Bool.sol b/packages/world/test/tables/Bool.sol index 6605ccc924..af0defcbfe 100644 --- a/packages/world/test/tables/Bool.sol +++ b/packages/world/test/tables/Bool.sol @@ -120,13 +120,13 @@ library Bool { } /** Tightly pack full data using this table's field layout */ - function encode(bool value) internal pure returns (bytes memory) { + function encode(bool value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol b/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol index 24ba4c5d0f..e6ebcd912f 100644 --- a/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/phaser/packages/contracts/src/codegen/tables/Counter.sol @@ -123,13 +123,13 @@ library Counter { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 value) internal pure returns (bytes memory) { + function encode(uint32 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/react/packages/contracts/src/codegen/tables/Counter.sol b/templates/react/packages/contracts/src/codegen/tables/Counter.sol index 24ba4c5d0f..e6ebcd912f 100644 --- a/templates/react/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/react/packages/contracts/src/codegen/tables/Counter.sol @@ -123,13 +123,13 @@ library Counter { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 value) internal pure returns (bytes memory) { + function encode(uint32 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/threejs/packages/contracts/src/codegen/tables/Position.sol b/templates/threejs/packages/contracts/src/codegen/tables/Position.sol index 1ea72f6f0b..b747e01351 100644 --- a/templates/threejs/packages/contracts/src/codegen/tables/Position.sol +++ b/templates/threejs/packages/contracts/src/codegen/tables/Position.sol @@ -242,8 +242,12 @@ library Position { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreSwitch.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data */ @@ -251,8 +255,12 @@ library Position { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = StoreCore.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Get the full data (using the specified store) */ @@ -260,8 +268,12 @@ library Position { bytes32[] memory _keyTuple = new bytes32[](1); _keyTuple[0] = key; - bytes memory _blob = _store.getRecord(_tableId, _keyTuple, _fieldLayout); - return decode(_blob); + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = _store.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** Set the full data using individual values */ @@ -318,13 +330,28 @@ library Position { set(_store, key, _table.x, _table.y, _table.z); } - /** Decode the tightly packed blob using this table's field layout */ - function decode(bytes memory _blob) internal pure returns (PositionData memory _table) { - _table.x = (int32(uint32(Bytes.slice4(_blob, 0)))); + /** + * Decode the tightly packed blob of static data using this table's field layout + * Undefined behaviour for invalid blobs + */ + function decodeStatic(bytes memory _blob) internal pure returns (int32 x, int32 y, int32 z) { + x = (int32(uint32(Bytes.slice4(_blob, 0)))); - _table.y = (int32(uint32(Bytes.slice4(_blob, 4)))); + y = (int32(uint32(Bytes.slice4(_blob, 4)))); - _table.z = (int32(uint32(Bytes.slice4(_blob, 8)))); + z = (int32(uint32(Bytes.slice4(_blob, 8)))); + } + + /** + * Decode the tightly packed blob using this table's field layout. + * Undefined behaviour for invalid blobs. + */ + function decode( + bytes memory _staticData, + PackedCounter, + bytes memory + ) internal pure returns (PositionData memory _table) { + (_table.x, _table.y, _table.z) = decodeStatic(_staticData); } /** Tightly pack static data using this table's schema */ @@ -333,13 +360,13 @@ library Position { } /** Tightly pack full data using this table's field layout */ - function encode(int32 x, int32 y, int32 z) internal pure returns (bytes memory) { + function encode(int32 x, int32 y, int32 z) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(x, y, z); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */ diff --git a/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol b/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol index 24ba4c5d0f..e6ebcd912f 100644 --- a/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol +++ b/templates/vanilla/packages/contracts/src/codegen/tables/Counter.sol @@ -123,13 +123,13 @@ library Counter { } /** Tightly pack full data using this table's field layout */ - function encode(uint32 value) internal pure returns (bytes memory) { + function encode(uint32 value) internal pure returns (bytes memory, PackedCounter, bytes memory) { bytes memory _staticData = encodeStatic(value); PackedCounter _encodedLengths; bytes memory _dynamicData; - return abi.encodePacked(_staticData, _encodedLengths, _dynamicData); + return (_staticData, _encodedLengths, _dynamicData); } /** Encode keys as a bytes32 array using this table's field layout */