diff --git a/.changeset/gorgeous-seals-smile.md b/.changeset/gorgeous-seals-smile.md new file mode 100644 index 0000000000..27f8576332 --- /dev/null +++ b/.changeset/gorgeous-seals-smile.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/store": patch +--- + +Patched `StoreRead.getDynamicFieldLength` to properly read `StoreCore.getDynamicFieldLength`. + +Previously `StoreRead.getDynamicFieldLength` incorrectly read from `StoreCore.getFieldLength`, which expected a `fieldIndex` instead of a `dynamicFieldIndex`, and thereby returned an invalid result if the table had both static and dynamic fields (in which case `fieldIndex` != `dynamicFieldIndex`). `StoreRead` is used for external reads from the `Store`/`World` contract, so this bug only materialized in external table reads (ie from `Systems` outside the root namespace) of the dynamic length of a field in a table with both static and dynamic fields. diff --git a/docs/pages/store/reference/misc.mdx b/docs/pages/store/reference/misc.mdx index 4f887c8baa..c50a76c213 100644 --- a/docs/pages/store/reference/misc.mdx +++ b/docs/pages/store/reference/misc.mdx @@ -2688,5 +2688,5 @@ Contains a constant representing the version of the Store protocol. _Identifier for the current Store protocol version._ ```solidity -bytes32 constant STORE_VERSION = "2.0.0"; +bytes32 constant STORE_VERSION = "2.0.1"; ``` diff --git a/docs/pages/world/reference/misc.mdx b/docs/pages/world/reference/misc.mdx index 6b7adfe3f7..f243fec51a 100644 --- a/docs/pages/world/reference/misc.mdx +++ b/docs/pages/world/reference/misc.mdx @@ -104,5 +104,5 @@ Contains a constant representing the version of the World protocol. _Identifier for the current World protocol version._ ```solidity -bytes32 constant WORLD_VERSION = "2.0.0"; +bytes32 constant WORLD_VERSION = "2.0.1"; ``` diff --git a/e2e/packages/contracts/mud.config.ts b/e2e/packages/contracts/mud.config.ts index 337c37e3e2..12b3b6fd5d 100644 --- a/e2e/packages/contracts/mud.config.ts +++ b/e2e/packages/contracts/mud.config.ts @@ -45,10 +45,18 @@ export default defineWorld({ }, StaticArray: { schema: { + num: "uint256", value: "uint256[3]", }, key: [], }, + DynamicArray: { + schema: { + num: "uint256", + value: "uint256[]", + }, + key: [], + }, }, modules: [ { diff --git a/e2e/packages/contracts/src/codegen/index.sol b/e2e/packages/contracts/src/codegen/index.sol index 8de689e3ae..51cc60222d 100644 --- a/e2e/packages/contracts/src/codegen/index.sol +++ b/e2e/packages/contracts/src/codegen/index.sol @@ -8,4 +8,5 @@ import { Vector, VectorData } from "./tables/Vector.sol"; import { NumberList } from "./tables/NumberList.sol"; import { Multi, MultiData } from "./tables/Multi.sol"; import { Position } from "./tables/Position.sol"; -import { StaticArray } from "./tables/StaticArray.sol"; +import { StaticArray, StaticArrayData } from "./tables/StaticArray.sol"; +import { DynamicArray, DynamicArrayData } from "./tables/DynamicArray.sol"; diff --git a/e2e/packages/contracts/src/codegen/tables/DynamicArray.sol b/e2e/packages/contracts/src/codegen/tables/DynamicArray.sol new file mode 100644 index 0000000000..867f3fac5a --- /dev/null +++ b/e2e/packages/contracts/src/codegen/tables/DynamicArray.sol @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import { IStore } from "@latticexyz/store/src/IStore.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { Memory } from "@latticexyz/store/src/Memory.sol"; +import { SliceLib } from "@latticexyz/store/src/Slice.sol"; +import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +struct DynamicArrayData { + uint256 num; + uint256[] value; +} + +library DynamicArray { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "", name: "DynamicArray", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x7462000000000000000000000000000044796e616d6963417272617900000000); + + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0020010120000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of () + Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256, uint256[]) + Schema constant _valueSchema = Schema.wrap(0x002001011f810000000000000000000000000000000000000000000000000000); + + /** + * @notice Get the table's key field names. + * @return keyNames An array of strings with the names of key fields. + */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](0); + } + + /** + * @notice Get the table's value field names. + * @return fieldNames An array of strings with the names of value fields. + */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](2); + fieldNames[0] = "num"; + fieldNames[1] = "value"; + } + + /** + * @notice Register the table with its config. + */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get num. + */ + function getNum() internal view returns (uint256 num) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Get num. + */ + function _getNum() internal view returns (uint256 num) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); + } + + /** + * @notice Set num. + */ + function setNum(uint256 num) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((num)), _fieldLayout); + } + + /** + * @notice Set num. + */ + function _setNum(uint256 num) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((num)), _fieldLayout); + } + + /** + * @notice Get value. + */ + function getValue() internal view returns (uint256[] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Get value. + */ + function _getValue() internal view returns (uint256[] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Set value. + */ + function setValue(uint256[] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((value))); + } + + /** + * @notice Set value. + */ + function _setValue(uint256[] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((value))); + } + + /** + * @notice Get the length of value. + */ + function lengthValue() internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 32; + } + } + + /** + * @notice Get the length of value. + */ + function _lengthValue() internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 32; + } + } + + /** + * @notice Get an item of value. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemValue(uint256 _index) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 32, (_index + 1) * 32); + return (uint256(bytes32(_blob))); + } + } + + /** + * @notice Get an item of value. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemValue(uint256 _index) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 32, (_index + 1) * 32); + return (uint256(bytes32(_blob))); + } + } + + /** + * @notice Push an element to value. + */ + function pushValue(uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to value. + */ + function _pushValue(uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Pop an element from value. + */ + function popValue() internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 32); + } + + /** + * @notice Pop an element from value. + */ + function _popValue() internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 32); + } + + /** + * @notice Update an element of value at `_index`. + */ + function updateValue(uint256 _index, uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of value at `_index`. + */ + function _updateValue(uint256 _index, uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Get the full data. + */ + function get() internal view returns (DynamicArrayData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Get the full data. + */ + function _get() internal view returns (DynamicArrayData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); + + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function set(uint256 num, uint256[] memory value) internal { + bytes memory _staticData = encodeStatic(num); + + EncodedLengths _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using individual values. + */ + function _set(uint256 num, uint256[] memory value) internal { + bytes memory _staticData = encodeStatic(num); + + EncodedLengths _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Set the full data using the data struct. + */ + function set(DynamicArrayData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.num); + + EncodedLengths _encodedLengths = encodeLengths(_table.value); + bytes memory _dynamicData = encodeDynamic(_table.value); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Set the full data using the data struct. + */ + function _set(DynamicArrayData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.num); + + EncodedLengths _encodedLengths = encodeLengths(_table.value); + bytes memory _dynamicData = encodeDynamic(_table.value); + + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of static data using this table's field layout. + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint256 num) { + num = (uint256(Bytes.getBytes32(_blob, 0))); + } + + /** + * @notice Decode the tightly packed blob of dynamic data using the encoded lengths. + */ + function decodeDynamic( + EncodedLengths _encodedLengths, + bytes memory _blob + ) internal pure returns (uint256[] memory value) { + uint256 _start; + uint256 _end; + unchecked { + _end = _encodedLengths.atIndex(0); + } + value = (SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint256()); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * @param _staticData Tightly packed static fields. + * @param _encodedLengths Encoded lengths of dynamic fields. + * @param _dynamicData Tightly packed dynamic fields. + */ + function decode( + bytes memory _staticData, + EncodedLengths _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (DynamicArrayData memory _table) { + (_table.num) = decodeStatic(_staticData); + + (_table.value) = decodeDynamic(_encodedLengths, _dynamicData); + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord() internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord() internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 num) internal pure returns (bytes memory) { + return abi.encodePacked(num); + } + + /** + * @notice Tightly pack dynamic data lengths using this table's schema. + * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). + */ + function encodeLengths(uint256[] memory value) internal pure returns (EncodedLengths _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = EncodedLengthsLib.pack(value.length * 32); + } + } + + /** + * @notice Tightly pack dynamic (variable length) data using this table's schema. + * @return The dynamic data, encoded into a sequence of bytes. + */ + function encodeDynamic(uint256[] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((value))); + } + + /** + * @notice Encode all of a record's fields. + * @return The static (fixed length) data, encoded into a sequence of bytes. + * @return The lengths of the dynamic fields (packed into a single bytes32 value). + * @return The dynamic (variable length) data, encoded into a sequence of bytes. + */ + function encode( + uint256 num, + uint256[] memory value + ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(num); + + EncodedLengths _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple() internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](0); + + return _keyTuple; + } +} diff --git a/e2e/packages/contracts/src/codegen/tables/StaticArray.sol b/e2e/packages/contracts/src/codegen/tables/StaticArray.sol index 1ee2570cff..7f0a3c817b 100644 --- a/e2e/packages/contracts/src/codegen/tables/StaticArray.sol +++ b/e2e/packages/contracts/src/codegen/tables/StaticArray.sol @@ -16,17 +16,22 @@ import { Schema } from "@latticexyz/store/src/Schema.sol"; import { EncodedLengths, EncodedLengthsLib } from "@latticexyz/store/src/EncodedLengths.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +struct StaticArrayData { + uint256 num; + uint256[3] value; +} + library StaticArray { // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "", name: "StaticArray", typeId: RESOURCE_TABLE });` ResourceId constant _tableId = ResourceId.wrap(0x7462000000000000000000000000000053746174696341727261790000000000); FieldLayout constant _fieldLayout = - FieldLayout.wrap(0x0000000100000000000000000000000000000000000000000000000000000000); + FieldLayout.wrap(0x0020010120000000000000000000000000000000000000000000000000000000); // Hex-encoded key schema of () Schema constant _keySchema = Schema.wrap(0x0000000000000000000000000000000000000000000000000000000000000000); - // Hex-encoded value schema of (uint256[]) - Schema constant _valueSchema = Schema.wrap(0x0000000181000000000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (uint256, uint256[]) + Schema constant _valueSchema = Schema.wrap(0x002001011f810000000000000000000000000000000000000000000000000000); /** * @notice Get the table's key field names. @@ -41,8 +46,9 @@ library StaticArray { * @return fieldNames An array of strings with the names of value fields. */ function getFieldNames() internal pure returns (string[] memory fieldNames) { - fieldNames = new string[](1); - fieldNames[0] = "value"; + fieldNames = new string[](2); + fieldNames[0] = "num"; + fieldNames[1] = "value"; } /** @@ -60,67 +66,67 @@ library StaticArray { } /** - * @notice Get value. + * @notice Get num. */ - function getValue() internal view returns (uint256[3] memory value) { + function getNum() internal view returns (uint256 num) { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); - return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); } /** - * @notice Get value. + * @notice Get num. */ - function _getValue() internal view returns (uint256[3] memory value) { + function _getNum() internal view returns (uint256 num) { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); - return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return (uint256(bytes32(_blob))); } /** - * @notice Get value. + * @notice Set num. */ - function get() internal view returns (uint256[3] memory value) { + function setNum(uint256 num) internal { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); - return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((num)), _fieldLayout); } /** - * @notice Get value. + * @notice Set num. */ - function _get() internal view returns (uint256[3] memory value) { + function _setNum(uint256 num) internal { bytes32[] memory _keyTuple = new bytes32[](0); - bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); - return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked((num)), _fieldLayout); } /** - * @notice Set value. + * @notice Get value. */ - function setValue(uint256[3] memory value) internal { + function getValue() internal view returns (uint256[3] memory value) { bytes32[] memory _keyTuple = new bytes32[](0); - StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_3(value))); + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); } /** - * @notice Set value. + * @notice Get value. */ - function _setValue(uint256[3] memory value) internal { + function _getValue() internal view returns (uint256[3] memory value) { bytes32[] memory _keyTuple = new bytes32[](0); - StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_3(value))); + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_3(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); } /** * @notice Set value. */ - function set(uint256[3] memory value) internal { + function setValue(uint256[3] memory value) internal { bytes32[] memory _keyTuple = new bytes32[](0); StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_3(value))); @@ -129,7 +135,7 @@ library StaticArray { /** * @notice Set value. */ - function _set(uint256[3] memory value) internal { + function _setValue(uint256[3] memory value) internal { bytes32[] memory _keyTuple = new bytes32[](0); StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_3(value))); @@ -138,9 +144,6 @@ library StaticArray { // The length of value uint256 constant lengthValue = 3; - // The length of value - uint256 constant length = 3; - /** * @notice Get an item of value. * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. @@ -184,93 +187,149 @@ library StaticArray { } /** - * @notice Get an item of value. - * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + * @notice Update an element of value at `_index`. */ - function getItem(uint256 _index) internal view returns (uint256) { + function updateValue(uint256 _index, uint256 _element) internal { bytes32[] memory _keyTuple = new bytes32[](0); - uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); - uint256 dynamicLength = _byteLength / 32; - uint256 staticLength = 3; - - if (_index < staticLength && _index >= dynamicLength) { - return (uint256(bytes32(new bytes(0)))); + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); } + } + + /** + * @notice Update an element of value at `_index`. + */ + function _updateValue(uint256 _index, uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); unchecked { - bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 32, (_index + 1) * 32); - return (uint256(bytes32(_blob))); + bytes memory _encoded = abi.encodePacked((_element)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); } } /** - * @notice Get an item of value. - * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + * @notice Get the full data. */ - function _getItem(uint256 _index) internal view returns (uint256) { + function get() internal view returns (StaticArrayData memory _table) { bytes32[] memory _keyTuple = new bytes32[](0); - uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); - uint256 dynamicLength = _byteLength / 32; - uint256 staticLength = 3; + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } - if (_index < staticLength && _index >= dynamicLength) { - return (uint256(bytes32(new bytes(0)))); - } + /** + * @notice Get the full data. + */ + function _get() internal view returns (StaticArrayData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](0); - unchecked { - bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 32, (_index + 1) * 32); - return (uint256(bytes32(_blob))); - } + (bytes memory _staticData, EncodedLengths _encodedLengths, bytes memory _dynamicData) = StoreCore.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); } /** - * @notice Update an element of value at `_index`. + * @notice Set the full data using individual values. */ - function updateValue(uint256 _index, uint256 _element) internal { + function set(uint256 num, uint256[3] memory value) internal { + bytes memory _staticData = encodeStatic(num); + + EncodedLengths _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); + bytes32[] memory _keyTuple = new bytes32[](0); - unchecked { - bytes memory _encoded = abi.encodePacked((_element)); - StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); - } + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); } /** - * @notice Update an element of value at `_index`. + * @notice Set the full data using individual values. */ - function _updateValue(uint256 _index, uint256 _element) internal { + function _set(uint256 num, uint256[3] memory value) internal { + bytes memory _staticData = encodeStatic(num); + + EncodedLengths _encodedLengths = encodeLengths(value); + bytes memory _dynamicData = encodeDynamic(value); + bytes32[] memory _keyTuple = new bytes32[](0); - unchecked { - bytes memory _encoded = abi.encodePacked((_element)); - StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); - } + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); } /** - * @notice Update an element of value at `_index`. + * @notice Set the full data using the data struct. */ - function update(uint256 _index, uint256 _element) internal { + function set(StaticArrayData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.num); + + EncodedLengths _encodedLengths = encodeLengths(_table.value); + bytes memory _dynamicData = encodeDynamic(_table.value); + bytes32[] memory _keyTuple = new bytes32[](0); - unchecked { - bytes memory _encoded = abi.encodePacked((_element)); - StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); - } + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); } /** - * @notice Update an element of value at `_index`. + * @notice Set the full data using the data struct. */ - function _update(uint256 _index, uint256 _element) internal { + function _set(StaticArrayData memory _table) internal { + bytes memory _staticData = encodeStatic(_table.num); + + EncodedLengths _encodedLengths = encodeLengths(_table.value); + bytes memory _dynamicData = encodeDynamic(_table.value); + bytes32[] memory _keyTuple = new bytes32[](0); + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** + * @notice Decode the tightly packed blob of static data using this table's field layout. + */ + function decodeStatic(bytes memory _blob) internal pure returns (uint256 num) { + num = (uint256(Bytes.getBytes32(_blob, 0))); + } + + /** + * @notice Decode the tightly packed blob of dynamic data using the encoded lengths. + */ + function decodeDynamic( + EncodedLengths _encodedLengths, + bytes memory _blob + ) internal pure returns (uint256[3] memory value) { + uint256 _start; + uint256 _end; unchecked { - bytes memory _encoded = abi.encodePacked((_element)); - StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 32), uint40(_encoded.length), _encoded); + _end = _encodedLengths.atIndex(0); } + value = toStaticArray_uint256_3(SliceLib.getSubslice(_blob, _start, _end).decodeArray_uint256()); + } + + /** + * @notice Decode the tightly packed blobs using this table's field layout. + * @param _staticData Tightly packed static fields. + * @param _encodedLengths Encoded lengths of dynamic fields. + * @param _dynamicData Tightly packed dynamic fields. + */ + function decode( + bytes memory _staticData, + EncodedLengths _encodedLengths, + bytes memory _dynamicData + ) internal pure returns (StaticArrayData memory _table) { + (_table.num) = decodeStatic(_staticData); + + (_table.value) = decodeDynamic(_encodedLengths, _dynamicData); } /** @@ -291,6 +350,14 @@ library StaticArray { StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); } + /** + * @notice Tightly pack static (fixed length) data using this table's schema. + * @return The static data, encoded into a sequence of bytes. + */ + function encodeStatic(uint256 num) internal pure returns (bytes memory) { + return abi.encodePacked(num); + } + /** * @notice Tightly pack dynamic data lengths using this table's schema. * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). @@ -316,8 +383,12 @@ library StaticArray { * @return The lengths of the dynamic fields (packed into a single bytes32 value). * @return The dynamic (variable length) data, encoded into a sequence of bytes. */ - function encode(uint256[3] memory value) internal pure returns (bytes memory, EncodedLengths, bytes memory) { - bytes memory _staticData; + function encode( + uint256 num, + uint256[3] memory value + ) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData = encodeStatic(num); + EncodedLengths _encodedLengths = encodeLengths(value); bytes memory _dynamicData = encodeDynamic(value); diff --git a/e2e/packages/contracts/test/ArrayLength.t.sol b/e2e/packages/contracts/test/ArrayLength.t.sol new file mode 100644 index 0000000000..fa65962d69 --- /dev/null +++ b/e2e/packages/contracts/test/ArrayLength.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { MudTest } from "@latticexyz/world/test/MudTest.t.sol"; + +import { IStoreErrors } from "@latticexyz/store/src/IStoreErrors.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { ROOT_NAMESPACE_ID } from "@latticexyz/world/src/constants.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; + +import { StaticArray, DynamicArray } from "../src/codegen/index.sol"; +import { toStaticArray_uint256_3 } from "../src/codegen/tables/StaticArray.sol"; + +/** + * @title GetItemValueWrapper + * @dev For testing that calling getItemValue properly reverts + * We use a seperate contract to ensure `expectRevert` does not only check the first external call + */ +contract GetItemValueWrapper { + function getItemValue(address worldAddress, uint256 _index) public { + StoreSwitch.setStoreAddress(worldAddress); + + StaticArray.getItemValue(_index); + } +} + +contract ArrayLengthTest is MudTest { + function testStaticArray() public { + assertEq(StaticArray.lengthValue, 3); + assertEq(StaticArray.getValue().length, 3); + + // Values within the static length return the default zeros value + assertEq(StaticArray.getValue()[0], 0); + assertEq(StaticArray.getItemValue(0), 0); + + assertEq(StaticArray.getValue()[1], 0); + assertEq(StaticArray.getItemValue(1), 0); + + assertEq(StaticArray.getValue()[2], 0); + assertEq(StaticArray.getItemValue(2), 0); + + // Values beyond the static length revert + GetItemValueWrapper wrapper = new GetItemValueWrapper(); + vm.expectRevert(abi.encodeWithSelector(IStoreErrors.Store_IndexOutOfBounds.selector, 0, 96)); + wrapper.getItemValue(worldAddress, 3); + + vm.expectRevert(abi.encodeWithSelector(IStoreErrors.Store_IndexOutOfBounds.selector, 0, 128)); + wrapper.getItemValue(worldAddress, 4); + + uint256[3] memory value = [uint256(1), 2, 3]; + vm.prank(NamespaceOwner.get(ROOT_NAMESPACE_ID)); + StaticArray.set(0, value); + + // Values within the static length return the correct value + assertEq(StaticArray.getValue()[0], 1); + assertEq(StaticArray.getItemValue(0), 1); + + assertEq(StaticArray.getValue()[1], 2); + assertEq(StaticArray.getItemValue(1), 2); + + assertEq(StaticArray.getValue()[2], 3); + assertEq(StaticArray.getItemValue(2), 3); + } + + function testDynamicArray() public { + assertEq(DynamicArray.lengthValue(), 0); + assertEq(DynamicArray.getValue().length, 0); + + // Values beyond the static length revert + GetItemValueWrapper wrapper = new GetItemValueWrapper(); + vm.expectRevert(abi.encodeWithSelector(IStoreErrors.Store_IndexOutOfBounds.selector, 0, 96)); + wrapper.getItemValue(worldAddress, 3); + + vm.expectRevert(abi.encodeWithSelector(IStoreErrors.Store_IndexOutOfBounds.selector, 0, 128)); + wrapper.getItemValue(worldAddress, 4); + + uint256[] memory value = new uint256[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + vm.prank(NamespaceOwner.get(ROOT_NAMESPACE_ID)); + DynamicArray.set(0, value); + + // Values within the static length return the correct value + assertEq(DynamicArray.getValue()[0], 1); + assertEq(DynamicArray.getItemValue(0), 1); + + assertEq(DynamicArray.getValue()[1], 2); + assertEq(DynamicArray.getItemValue(1), 2); + + assertEq(DynamicArray.getValue()[2], 3); + assertEq(DynamicArray.getItemValue(2), 3); + } +} diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index aad107a045..a13035cc6e 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -25,8 +25,8 @@ export const worldDeployEvents = [helloStoreEvent, helloWorldEvent] as const; export const worldAbi = [...IBaseWorldAbi, ...IModuleAbi] as const; // Ideally, this should be an append-only list. Before adding more versions here, be sure to add backwards-compatible support for old Store/World versions. -export const supportedStoreVersions = ["2.0.0"]; -export const supportedWorldVersions = ["2.0.0"]; +export const supportedStoreVersions = ["2.0.0", "2.0.1"]; +export const supportedWorldVersions = ["2.0.0", "2.0.1"]; // TODO: extend this to include factory+deployer address? so we can reuse the deployer for a world? export type WorldDeploy = { diff --git a/packages/store/src/StoreRead.sol b/packages/store/src/StoreRead.sol index 34cfd0abe9..95427d9151 100644 --- a/packages/store/src/StoreRead.sol +++ b/packages/store/src/StoreRead.sol @@ -185,7 +185,7 @@ contract StoreRead is IStoreRead { bytes32[] memory keyTuple, uint8 dynamicFieldIndex ) public view virtual returns (uint256) { - return StoreCore.getFieldLength(tableId, keyTuple, dynamicFieldIndex); + return StoreCore.getDynamicFieldLength(tableId, keyTuple, dynamicFieldIndex); } /** diff --git a/packages/store/src/version.sol b/packages/store/src/version.sol index c124e9f542..3e0a984e7a 100644 --- a/packages/store/src/version.sol +++ b/packages/store/src/version.sol @@ -8,4 +8,4 @@ pragma solidity >=0.8.24; */ /// @dev Identifier for the current Store protocol version. -bytes32 constant STORE_VERSION = "2.0.0"; +bytes32 constant STORE_VERSION = "2.0.1"; diff --git a/packages/store/ts/protocol-snapshots/2.0.1.snap b/packages/store/ts/protocol-snapshots/2.0.1.snap new file mode 100644 index 0000000000..21c6ecc745 --- /dev/null +++ b/packages/store/ts/protocol-snapshots/2.0.1.snap @@ -0,0 +1,57 @@ +[ + "error EncodedLengths_InvalidLength(uint256 length)", + "error FieldLayout_Empty()", + "error FieldLayout_InvalidStaticDataLength(uint256 staticDataLength, uint256 computedStaticDataLength)", + "error FieldLayout_StaticLengthDoesNotFitInAWord(uint256 index)", + "error FieldLayout_StaticLengthIsNotZero(uint256 index)", + "error FieldLayout_StaticLengthIsZero(uint256 index)", + "error FieldLayout_TooManyDynamicFields(uint256 numFields, uint256 maxFields)", + "error FieldLayout_TooManyFields(uint256 numFields, uint256 maxFields)", + "error Schema_InvalidLength(uint256 length)", + "error Schema_StaticTypeAfterDynamicType()", + "error Slice_OutOfBounds(bytes data, uint256 start, uint256 end)", + "error Store_IndexOutOfBounds(uint256 length, uint256 accessedIndex)", + "error Store_InvalidBounds(uint256 start, uint256 end)", + "error Store_InvalidFieldNamesLength(uint256 expected, uint256 received)", + "error Store_InvalidKeyNamesLength(uint256 expected, uint256 received)", + "error Store_InvalidResourceType(bytes2 expected, bytes32 resourceId, string resourceIdString)", + "error Store_InvalidSplice(uint40 startWithinField, uint40 deleteCount, uint40 fieldLength)", + "error Store_InvalidStaticDataLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaDynamicLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaStaticLength(uint256 expected, uint256 received)", + "error Store_TableAlreadyExists(bytes32 tableId, string tableIdString)", + "error Store_TableNotFound(bytes32 tableId, string tableIdString)", + "event HelloStore(bytes32 indexed storeVersion)", + "event Store_DeleteRecord(bytes32 indexed tableId, bytes32[] keyTuple)", + "event Store_SetRecord(bytes32 indexed tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "event Store_SpliceDynamicData(bytes32 indexed tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint48 start, uint40 deleteCount, bytes32 encodedLengths, bytes data)", + "event Store_SpliceStaticData(bytes32 indexed tableId, bytes32[] keyTuple, uint48 start, bytes data)", + "function deleteRecord(bytes32 tableId, bytes32[] keyTuple)", + "function getDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (bytes)", + "function getDynamicFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (uint256)", + "function getDynamicFieldSlice(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 start, uint256 end) view returns (bytes data)", + "function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes data)", + "function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (bytes data)", + "function getFieldLayout(bytes32 tableId) view returns (bytes32 fieldLayout)", + "function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (uint256)", + "function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (uint256)", + "function getKeySchema(bytes32 tableId) view returns (bytes32 keySchema)", + "function getRecord(bytes32 tableId, bytes32[] keyTuple, bytes32 fieldLayout) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function getRecord(bytes32 tableId, bytes32[] keyTuple) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function getStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes32)", + "function getValueSchema(bytes32 tableId) view returns (bytes32 valueSchema)", + "function popFromDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 byteLengthToPop)", + "function pushToDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes dataToPush)", + "function registerStoreHook(bytes32 tableId, address hookAddress, uint8 enabledHooksBitmap)", + "function registerTable(bytes32 tableId, bytes32 fieldLayout, bytes32 keySchema, bytes32 valueSchema, string[] keyNames, string[] fieldNames)", + "function setDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes data)", + "function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)", + "function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data)", + "function setRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function setStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)", + "function spliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint40 startWithinField, uint40 deleteCount, bytes data)", + "function spliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, bytes data)", + "function storeVersion() view returns (bytes32 version)", + "function unregisterStoreHook(bytes32 tableId, address hookAddress)", +] \ No newline at end of file diff --git a/packages/store/ts/protocolVersions.ts b/packages/store/ts/protocolVersions.ts index 5fcdce84b4..f99100a48a 100644 --- a/packages/store/ts/protocolVersions.ts +++ b/packages/store/ts/protocolVersions.ts @@ -1,4 +1,5 @@ // History of protocol versions and a short description of what changed in each. export const protocolVersions = { + "2.0.1": "Patched `StoreRead.getDynamicFieldLength` to use the correct method to read the dynamic field length.", "2.0.0": "Initial v2 release. See mud.dev/changelog for the full list of changes from v1.", }; diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index 54fa5bbf75..2b9a55876e 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -99,7 +99,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 194431 + "gasUsed": 191359 }, { "file": "test/KeysInTableModule.t.sol", @@ -123,7 +123,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 226515 + "gasUsed": 223443 }, { "file": "test/KeysInTableModule.t.sol", @@ -141,7 +141,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 144756 + "gasUsed": 141684 }, { "file": "test/KeysWithValueModule.t.sol", @@ -213,31 +213,31 @@ "file": "test/query.t.sol", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", - "gasUsed": 96338 + "gasUsed": 93122 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", - "gasUsed": 49724 + "gasUsed": 48652 }, { "file": "test/query.t.sol", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", - "gasUsed": 119185 + "gasUsed": 113825 }, { "file": "test/query.t.sol", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", - "gasUsed": 76121 + "gasUsed": 71833 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", - "gasUsed": 77523 + "gasUsed": 74307 }, { "file": "test/query.t.sol", @@ -249,19 +249,19 @@ "file": "test/query.t.sol", "test": "testHasQuery", "name": "HasQuery", - "gasUsed": 17052 + "gasUsed": 15980 }, { "file": "test/query.t.sol", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", - "gasUsed": 5342895 + "gasUsed": 5341823 }, { "file": "test/query.t.sol", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", - "gasUsed": 505577 + "gasUsed": 504505 }, { "file": "test/query.t.sol", @@ -273,7 +273,7 @@ "file": "test/query.t.sol", "test": "testNotValueQuery", "name": "NotValueQuery", - "gasUsed": 43961 + "gasUsed": 42889 }, { "file": "test/StandardDelegationsModule.t.sol", diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 70f6d42296..e22ab896de 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -57,13 +57,13 @@ "file": "test/Factories.t.sol", "test": "testCreate2Factory", "name": "deploy contract via Create2", - "gasUsed": 4960875 + "gasUsed": 4970818 }, { "file": "test/Factories.t.sol", "test": "testWorldFactoryGas", "name": "deploy world via WorldFactory", - "gasUsed": 12796213 + "gasUsed": 12805251 }, { "file": "test/World.t.sol", diff --git a/packages/world/src/version.sol b/packages/world/src/version.sol index 1882e51272..15315b6673 100644 --- a/packages/world/src/version.sol +++ b/packages/world/src/version.sol @@ -8,4 +8,4 @@ pragma solidity >=0.8.24; */ /// @dev Identifier for the current World protocol version. -bytes32 constant WORLD_VERSION = "2.0.0"; +bytes32 constant WORLD_VERSION = "2.0.1"; diff --git a/packages/world/ts/protocol-snapshots/2.0.1.snap b/packages/world/ts/protocol-snapshots/2.0.1.snap new file mode 100644 index 0000000000..aed3400889 --- /dev/null +++ b/packages/world/ts/protocol-snapshots/2.0.1.snap @@ -0,0 +1,102 @@ +[ + "error EncodedLengths_InvalidLength(uint256 length)", + "error FieldLayout_Empty()", + "error FieldLayout_InvalidStaticDataLength(uint256 staticDataLength, uint256 computedStaticDataLength)", + "error FieldLayout_StaticLengthDoesNotFitInAWord(uint256 index)", + "error FieldLayout_StaticLengthIsNotZero(uint256 index)", + "error FieldLayout_StaticLengthIsZero(uint256 index)", + "error FieldLayout_TooManyDynamicFields(uint256 numFields, uint256 maxFields)", + "error FieldLayout_TooManyFields(uint256 numFields, uint256 maxFields)", + "error Module_AlreadyInstalled()", + "error Module_MissingDependency(address dependency)", + "error Module_NonRootInstallNotSupported()", + "error Module_RootInstallNotSupported()", + "error Schema_InvalidLength(uint256 length)", + "error Schema_StaticTypeAfterDynamicType()", + "error Slice_OutOfBounds(bytes data, uint256 start, uint256 end)", + "error Store_IndexOutOfBounds(uint256 length, uint256 accessedIndex)", + "error Store_InvalidBounds(uint256 start, uint256 end)", + "error Store_InvalidFieldNamesLength(uint256 expected, uint256 received)", + "error Store_InvalidKeyNamesLength(uint256 expected, uint256 received)", + "error Store_InvalidResourceType(bytes2 expected, bytes32 resourceId, string resourceIdString)", + "error Store_InvalidSplice(uint40 startWithinField, uint40 deleteCount, uint40 fieldLength)", + "error Store_InvalidStaticDataLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaDynamicLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaLength(uint256 expected, uint256 received)", + "error Store_InvalidValueSchemaStaticLength(uint256 expected, uint256 received)", + "error Store_TableAlreadyExists(bytes32 tableId, string tableIdString)", + "error Store_TableNotFound(bytes32 tableId, string tableIdString)", + "error World_AccessDenied(string resource, address caller)", + "error World_AlreadyInitialized()", + "error World_CallbackNotAllowed(bytes4 functionSelector)", + "error World_DelegationNotFound(address delegator, address delegatee)", + "error World_FunctionSelectorAlreadyExists(bytes4 functionSelector)", + "error World_FunctionSelectorNotFound(bytes4 functionSelector)", + "error World_InsufficientBalance(uint256 balance, uint256 amount)", + "error World_InterfaceNotSupported(address contractAddress, bytes4 interfaceId)", + "error World_InvalidNamespace(bytes14 namespace)", + "error World_InvalidResourceId(bytes32 resourceId, string resourceIdString)", + "error World_InvalidResourceType(bytes2 expected, bytes32 resourceId, string resourceIdString)", + "error World_ResourceAlreadyExists(bytes32 resourceId, string resourceIdString)", + "error World_ResourceNotFound(bytes32 resourceId, string resourceIdString)", + "error World_SystemAlreadyExists(address system)", + "error World_UnlimitedDelegationNotAllowed()", + "event HelloStore(bytes32 indexed storeVersion)", + "event HelloWorld(bytes32 indexed worldVersion)", + "event Store_DeleteRecord(bytes32 indexed tableId, bytes32[] keyTuple)", + "event Store_SetRecord(bytes32 indexed tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "event Store_SpliceDynamicData(bytes32 indexed tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint48 start, uint40 deleteCount, bytes32 encodedLengths, bytes data)", + "event Store_SpliceStaticData(bytes32 indexed tableId, bytes32[] keyTuple, uint48 start, bytes data)", + "function batchCall((bytes32 systemId, bytes callData)[] systemCalls) returns (bytes[] returnDatas)", + "function batchCallFrom((address from, bytes32 systemId, bytes callData)[] systemCalls) returns (bytes[] returnDatas)", + "function call(bytes32 systemId, bytes callData) payable returns (bytes)", + "function callFrom(address delegator, bytes32 systemId, bytes callData) payable returns (bytes)", + "function creator() view returns (address)", + "function deleteRecord(bytes32 tableId, bytes32[] keyTuple)", + "function getDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (bytes)", + "function getDynamicFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex) view returns (uint256)", + "function getDynamicFieldSlice(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 start, uint256 end) view returns (bytes data)", + "function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes data)", + "function getField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (bytes data)", + "function getFieldLayout(bytes32 tableId) view returns (bytes32 fieldLayout)", + "function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (uint256)", + "function getFieldLength(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex) view returns (uint256)", + "function getKeySchema(bytes32 tableId) view returns (bytes32 keySchema)", + "function getRecord(bytes32 tableId, bytes32[] keyTuple, bytes32 fieldLayout) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function getRecord(bytes32 tableId, bytes32[] keyTuple) view returns (bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function getStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes32 fieldLayout) view returns (bytes32)", + "function getValueSchema(bytes32 tableId) view returns (bytes32 valueSchema)", + "function grantAccess(bytes32 resourceId, address grantee)", + "function initialize(address initModule)", + "function installModule(address module, bytes encodedArgs)", + "function installRootModule(address module, bytes encodedArgs)", + "function popFromDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint256 byteLengthToPop)", + "function pushToDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes dataToPush)", + "function registerDelegation(address delegatee, bytes32 delegationControlId, bytes initCallData)", + "function registerFunctionSelector(bytes32 systemId, string systemFunctionSignature) returns (bytes4 worldFunctionSelector)", + "function registerNamespace(bytes32 namespaceId)", + "function registerNamespaceDelegation(bytes32 namespaceId, bytes32 delegationControlId, bytes initCallData)", + "function registerRootFunctionSelector(bytes32 systemId, string worldFunctionSignature, string systemFunctionSignature) returns (bytes4 worldFunctionSelector)", + "function registerStoreHook(bytes32 tableId, address hookAddress, uint8 enabledHooksBitmap)", + "function registerSystem(bytes32 systemId, address system, bool publicAccess)", + "function registerSystemHook(bytes32 systemId, address hookAddress, uint8 enabledHooksBitmap)", + "function registerTable(bytes32 tableId, bytes32 fieldLayout, bytes32 keySchema, bytes32 valueSchema, string[] keyNames, string[] fieldNames)", + "function renounceOwnership(bytes32 namespaceId)", + "function revokeAccess(bytes32 resourceId, address grantee)", + "function setDynamicField(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, bytes data)", + "function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)", + "function setField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data)", + "function setRecord(bytes32 tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData)", + "function setStaticField(bytes32 tableId, bytes32[] keyTuple, uint8 fieldIndex, bytes data, bytes32 fieldLayout)", + "function spliceDynamicData(bytes32 tableId, bytes32[] keyTuple, uint8 dynamicFieldIndex, uint40 startWithinField, uint40 deleteCount, bytes data)", + "function spliceStaticData(bytes32 tableId, bytes32[] keyTuple, uint48 start, bytes data)", + "function storeVersion() view returns (bytes32 version)", + "function transferBalanceToAddress(bytes32 fromNamespaceId, address toAddress, uint256 amount)", + "function transferBalanceToNamespace(bytes32 fromNamespaceId, bytes32 toNamespaceId, uint256 amount)", + "function transferOwnership(bytes32 namespaceId, address newOwner)", + "function unregisterDelegation(address delegatee)", + "function unregisterNamespaceDelegation(bytes32 namespaceId)", + "function unregisterStoreHook(bytes32 tableId, address hookAddress)", + "function unregisterSystemHook(bytes32 systemId, address hookAddress)", + "function worldVersion() view returns (bytes32)", +] \ No newline at end of file diff --git a/packages/world/ts/protocolVersions.ts b/packages/world/ts/protocolVersions.ts index 5fcdce84b4..f99100a48a 100644 --- a/packages/world/ts/protocolVersions.ts +++ b/packages/world/ts/protocolVersions.ts @@ -1,4 +1,5 @@ // History of protocol versions and a short description of what changed in each. export const protocolVersions = { + "2.0.1": "Patched `StoreRead.getDynamicFieldLength` to use the correct method to read the dynamic field length.", "2.0.0": "Initial v2 release. See mud.dev/changelog for the full list of changes from v1.", };