diff --git a/.changeset/plenty-rules-pull.md b/.changeset/plenty-rules-pull.md new file mode 100644 index 0000000000..4ca13996f0 --- /dev/null +++ b/.changeset/plenty-rules-pull.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/schema-type": minor +--- + +Added `isSchemaAbiType` helper function to check and narrow an unknown string to the `SchemaAbiType` type diff --git a/.changeset/sixty-crabs-itch.md b/.changeset/sixty-crabs-itch.md new file mode 100644 index 0000000000..86a083cb62 --- /dev/null +++ b/.changeset/sixty-crabs-itch.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/store": patch +--- + +Fixed `resolveUserTypes` for static arrays. +`resolveUserTypes` is used by `deploy`, which prevented deploying tables with static arrays. diff --git a/e2e/packages/contracts/mud.config.ts b/e2e/packages/contracts/mud.config.ts index 712396e1dd..23841e30aa 100644 --- a/e2e/packages/contracts/mud.config.ts +++ b/e2e/packages/contracts/mud.config.ts @@ -47,5 +47,9 @@ export default mudConfig({ player: "address", }, }, + StaticArray: { + keySchema: {}, + valueSchema: "uint256[2]", + }, }, }); diff --git a/e2e/packages/contracts/src/codegen/index.sol b/e2e/packages/contracts/src/codegen/index.sol index acc22835c2..32762ce31a 100644 --- a/e2e/packages/contracts/src/codegen/index.sol +++ b/e2e/packages/contracts/src/codegen/index.sol @@ -8,3 +8,4 @@ import { Vector, VectorData, VectorTableId } from "./tables/Vector.sol"; import { NumberList, NumberListTableId } from "./tables/NumberList.sol"; import { Multi, MultiData, MultiTableId } from "./tables/Multi.sol"; import { Position, PositionTableId } from "./tables/Position.sol"; +import { StaticArray, StaticArrayTableId } from "./tables/StaticArray.sol"; diff --git a/e2e/packages/contracts/src/codegen/tables/StaticArray.sol b/e2e/packages/contracts/src/codegen/tables/StaticArray.sol new file mode 100644 index 0000000000..7dd01137ff --- /dev/null +++ b/e2e/packages/contracts/src/codegen/tables/StaticArray.sol @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/* Autogenerated file. Do not edit manually. */ + +// Import schema type +import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; + +// 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, FieldLayoutLib } from "@latticexyz/store/src/FieldLayout.sol"; +import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; +import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE, RESOURCE_OFFCHAIN_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; + +ResourceId constant _tableId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, bytes14(""), bytes16("StaticArray"))) +); +ResourceId constant StaticArrayTableId = _tableId; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x0000000100000000000000000000000000000000000000000000000000000000 +); + +library StaticArray { + /** + * @notice Get the table values' field layout. + * @return _fieldLayout The field layout for the table. + */ + function getFieldLayout() internal pure returns (FieldLayout) { + return _fieldLayout; + } + + /** + * @notice Get the table's key schema. + * @return _keySchema The key schema for the table. + */ + function getKeySchema() internal pure returns (Schema) { + SchemaType[] memory _keySchema = new SchemaType[](0); + + return SchemaLib.encode(_keySchema); + } + + /** + * @notice Get the table's value schema. + * @return _valueSchema The value schema for the table. + */ + function getValueSchema() internal pure returns (Schema) { + SchemaType[] memory _valueSchema = new SchemaType[](1); + _valueSchema[0] = SchemaType.UINT256_ARRAY; + + return SchemaLib.encode(_valueSchema); + } + + /** + * @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[](1); + fieldNames[0] = "value"; + } + + /** + * @notice Register the table with its config. + */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** + * @notice Register the table with its config. + */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** + * @notice Get value. + */ + function getValue() internal view returns (uint256[2] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_2(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Get value. + */ + function _getValue() internal view returns (uint256[2] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_2(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Get value. + */ + function get() internal view returns (uint256[2] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_2(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Get value. + */ + function _get() internal view returns (uint256[2] memory value) { + bytes32[] memory _keyTuple = new bytes32[](0); + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return toStaticArray_uint256_2(SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_uint256()); + } + + /** + * @notice Set value. + */ + function setValue(uint256[2] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_2(value))); + } + + /** + * @notice Set value. + */ + function _setValue(uint256[2] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_2(value))); + } + + /** + * @notice Set value. + */ + function set(uint256[2] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_2(value))); + } + + /** + * @notice Set value. + */ + function _set(uint256[2] memory value) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode(fromStaticArray_uint256_2(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 the length of value. + */ + function length() 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 _length() 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 Get an item of value. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItem(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 _getItem(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 Push an element to value. + */ + function push(uint256 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to value. + */ + function _push(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 Pop an element from value. + */ + function pop() internal { + bytes32[] memory _keyTuple = new bytes32[](0); + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 32); + } + + /** + * @notice Pop an element from value. + */ + function _pop() 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 Update an element of value at `_index`. + */ + function update(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 _update(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 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 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[2] memory value) internal pure returns (PackedCounter _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = PackedCounterLib.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[2] memory value) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode(fromStaticArray_uint256_2(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 dyanmic (variable length) data, encoded into a sequence of bytes. + */ + function encode(uint256[2] memory value) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData; + PackedCounter _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; + } +} + +/** + * @notice Cast a dynamic array to a static array. + * @dev In memory static arrays are just dynamic arrays without the 32 length bytes, + * so this function moves the pointer to the first element of the dynamic array. + * If the length of the dynamic array is smaller than the static length, + * the function returns an uninitialized array to avoid memory corruption. + * @param _value The dynamic array to cast. + * @return _result The static array. + */ +function toStaticArray_uint256_2(uint256[] memory _value) pure returns (uint256[2] memory _result) { + if (_value.length < 2) { + // return an uninitialized array if the length is smaller than the fixed length to avoid memory corruption + return _result; + } else { + // in memory static arrays are just dynamic arrays without the 32 length bytes + // (without the length check this could lead to memory corruption) + assembly { + _result := add(_value, 0x20) + } + } +} + +/** + * @notice Copy a static array to a dynamic array. + * @dev Static arrays don't have a length prefix, so this function copies the memory from the static array to a new dynamic array. + * @param _value The static array to copy. + * @return _result The dynamic array. + */ +function fromStaticArray_uint256_2(uint256[2] memory _value) pure returns (uint256[] memory _result) { + _result = new uint256[](2); + uint256 fromPointer; + uint256 toPointer; + assembly { + fromPointer := _value + toPointer := add(_result, 0x20) + } + Memory.copy(fromPointer, toPointer, 64); +} diff --git a/packages/schema-type/src/typescript/schemaAbiTypes.ts b/packages/schema-type/src/typescript/schemaAbiTypes.ts index c7f3886e15..58792943c5 100644 --- a/packages/schema-type/src/typescript/schemaAbiTypes.ts +++ b/packages/schema-type/src/typescript/schemaAbiTypes.ts @@ -211,3 +211,7 @@ export const dynamicAbiTypes = schemaAbiTypes.slice(98) as any as TupleSplit(schema: TSchema, userTypes: TUserTypes): ResolvedSchema { const resolvedSchema: Record = {}; for (const [key, value] of Object.entries(schema)) { - resolvedSchema[key] = (userTypes[value]?.internalType as SchemaAbiType) ?? value; + if (isSchemaAbiType(value)) { + resolvedSchema[key] = value; + } else if (userTypes[value] !== undefined) { + resolvedSchema[key] = userTypes[value].internalType as SchemaAbiType; + } else { + const staticArray = parseStaticArray(value); + if (!staticArray) throw new Error(`Unexpected type: ${value}`); + resolvedSchema[key] = `${staticArray.elementType as StaticAbiType}[]`; + } } return resolvedSchema as ResolvedSchema; }