diff --git a/.changeset/nice-fishes-perform.md b/.changeset/nice-fishes-perform.md new file mode 100644 index 0000000000..5ae4ad1356 --- /dev/null +++ b/.changeset/nice-fishes-perform.md @@ -0,0 +1,10 @@ +--- +"@latticexyz/store": major +--- + +These breaking changes only affect store utilities, you aren't affected if you use `@latticexyz/cli` codegen scripts. + +- Add `remappings` argument to the `tablegen` codegen function, so that it can read user-provided files. +- In `RenderTableOptions` change the type of `imports` from `RelativeImportDatum` to `ImportDatum`, to allow passing absolute imports to the table renderer. +- Add `solidityUserTypes` argument to several functions that need to resolve user or abi types: `resolveAbiOrUserType`, `importForAbiOrUserType`, `getUserTypeInfo`. +- Add `userTypes` config option to MUD config, which takes user types mapped to file paths from which to import them. diff --git a/.changeset/popular-coins-invent.md b/.changeset/popular-coins-invent.md new file mode 100644 index 0000000000..6d1f32a09d --- /dev/null +++ b/.changeset/popular-coins-invent.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/common": minor +--- + +- Add `getRemappings` to get foundry remappings as an array of `[to, from]` tuples. +- Add `extractUserTypes` solidity parser utility to extract user-defined types. +- Add `loadAndExtractUserTypes` helper to load and parse a solidity file, extracting user-defined types. diff --git a/packages/cli/contracts/src/codegen/index.sol b/packages/cli/contracts/src/codegen/index.sol index f3298837cf..fb31045cfb 100644 --- a/packages/cli/contracts/src/codegen/index.sol +++ b/packages/cli/contracts/src/codegen/index.sol @@ -8,3 +8,4 @@ import { Dynamics1, Dynamics1Data, Dynamics1TableId } from "./tables/Dynamics1.s import { Dynamics2, Dynamics2Data, Dynamics2TableId } from "./tables/Dynamics2.sol"; import { Singleton, SingletonTableId } from "./tables/Singleton.sol"; import { Offchain, OffchainTableId } from "./tables/Offchain.sol"; +import { UserTyped, UserTypedData, UserTypedTableId } from "./tables/UserTyped.sol"; diff --git a/packages/cli/contracts/src/codegen/tables/UserTyped.sol b/packages/cli/contracts/src/codegen/tables/UserTyped.sol new file mode 100644 index 0000000000..fd2d41f421 --- /dev/null +++ b/packages/cli/contracts/src/codegen/tables/UserTyped.sol @@ -0,0 +1,1104 @@ +// 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"; + +// Import user types +import { TestTypeAddress, TestTypeInt64, TestTypeLibrary } from "./../../types.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + +ResourceId constant _tableId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, bytes14(""), bytes16("UserTyped"))) +); +ResourceId constant UserTypedTableId = _tableId; + +FieldLayout constant _fieldLayout = FieldLayout.wrap( + 0x004d050014080110200000000000000000000000000000000000000000000000 +); + +struct UserTypedData { + TestTypeAddress v1; + TestTypeInt64 v2; + TestTypeLibrary.TestTypeBool v3; + TestTypeLibrary.TestTypeUint128 v4; + ResourceId v5; +} + +library UserTyped { + /** Get the table values' field layout */ + function getFieldLayout() internal pure returns (FieldLayout) { + return _fieldLayout; + } + + /** Get the table's key schema */ + function getKeySchema() internal pure returns (Schema) { + SchemaType[] memory _keySchema = new SchemaType[](5); + _keySchema[0] = SchemaType.ADDRESS; + _keySchema[1] = SchemaType.INT64; + _keySchema[2] = SchemaType.BOOL; + _keySchema[3] = SchemaType.UINT128; + _keySchema[4] = SchemaType.BYTES32; + + return SchemaLib.encode(_keySchema); + } + + /** Get the table's value schema */ + function getValueSchema() internal pure returns (Schema) { + SchemaType[] memory _valueSchema = new SchemaType[](5); + _valueSchema[0] = SchemaType.ADDRESS; + _valueSchema[1] = SchemaType.INT64; + _valueSchema[2] = SchemaType.BOOL; + _valueSchema[3] = SchemaType.UINT128; + _valueSchema[4] = SchemaType.BYTES32; + + return SchemaLib.encode(_valueSchema); + } + + /** Get the table's key names */ + function getKeyNames() internal pure returns (string[] memory keyNames) { + keyNames = new string[](5); + keyNames[0] = "k1"; + keyNames[1] = "k2"; + keyNames[2] = "k3"; + keyNames[3] = "k4"; + keyNames[4] = "k5"; + } + + /** Get the table's field names */ + function getFieldNames() internal pure returns (string[] memory fieldNames) { + fieldNames = new string[](5); + fieldNames[0] = "v1"; + fieldNames[1] = "v2"; + fieldNames[2] = "v3"; + fieldNames[3] = "v4"; + fieldNames[4] = "v5"; + } + + /** Register the table with its config */ + function register() internal { + StoreSwitch.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Register the table with its config */ + function _register() internal { + StoreCore.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Register the table with its config (using the specified store) */ + function register(IStore _store) internal { + _store.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); + } + + /** Get v1 */ + function getV1( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeAddress v1) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return TestTypeAddress.wrap(address(bytes20(_blob))); + } + + /** Get v1 */ + function _getV1( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeAddress v1) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return TestTypeAddress.wrap(address(bytes20(_blob))); + } + + /** Get v1 (using the specified store) */ + function getV1( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeAddress v1) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return TestTypeAddress.wrap(address(bytes20(_blob))); + } + + /** Set v1 */ + function setV1( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked(TestTypeAddress.unwrap(v1)), _fieldLayout); + } + + /** Set v1 */ + function _setV1( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked(TestTypeAddress.unwrap(v1)), _fieldLayout); + } + + /** Set v1 (using the specified store) */ + function setV1( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setStaticField(_tableId, _keyTuple, 0, abi.encodePacked(TestTypeAddress.unwrap(v1)), _fieldLayout); + } + + /** Get v2 */ + function getV2( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeInt64 v2) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return TestTypeInt64.wrap(int64(uint64(bytes8(_blob)))); + } + + /** Get v2 */ + function _getV2( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeInt64 v2) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return TestTypeInt64.wrap(int64(uint64(bytes8(_blob)))); + } + + /** Get v2 (using the specified store) */ + function getV2( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeInt64 v2) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 1, _fieldLayout); + return TestTypeInt64.wrap(int64(uint64(bytes8(_blob)))); + } + + /** Set v2 */ + function setV2( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeInt64 v2 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked(TestTypeInt64.unwrap(v2)), _fieldLayout); + } + + /** Set v2 */ + function _setV2( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeInt64 v2 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked(TestTypeInt64.unwrap(v2)), _fieldLayout); + } + + /** Set v2 (using the specified store) */ + function setV2( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeInt64 v2 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setStaticField(_tableId, _keyTuple, 1, abi.encodePacked(TestTypeInt64.unwrap(v2)), _fieldLayout); + } + + /** Get v3 */ + function getV3( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeBool v3) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return TestTypeLibrary.TestTypeBool.wrap(_toBool(uint8(bytes1(_blob)))); + } + + /** Get v3 */ + function _getV3( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeBool v3) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return TestTypeLibrary.TestTypeBool.wrap(_toBool(uint8(bytes1(_blob)))); + } + + /** Get v3 (using the specified store) */ + function getV3( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeBool v3) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 2, _fieldLayout); + return TestTypeLibrary.TestTypeBool.wrap(_toBool(uint8(bytes1(_blob)))); + } + + /** Set v3 */ + function setV3( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeBool v3 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setStaticField( + _tableId, + _keyTuple, + 2, + abi.encodePacked(TestTypeLibrary.TestTypeBool.unwrap(v3)), + _fieldLayout + ); + } + + /** Set v3 */ + function _setV3( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeBool v3 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setStaticField( + _tableId, + _keyTuple, + 2, + abi.encodePacked(TestTypeLibrary.TestTypeBool.unwrap(v3)), + _fieldLayout + ); + } + + /** Set v3 (using the specified store) */ + function setV3( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeBool v3 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setStaticField( + _tableId, + _keyTuple, + 2, + abi.encodePacked(TestTypeLibrary.TestTypeBool.unwrap(v3)), + _fieldLayout + ); + } + + /** Get v4 */ + function getV4( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeUint128 v4) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); + return TestTypeLibrary.TestTypeUint128.wrap(uint128(bytes16(_blob))); + } + + /** Get v4 */ + function _getV4( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeUint128 v4) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); + return TestTypeLibrary.TestTypeUint128.wrap(uint128(bytes16(_blob))); + } + + /** Get v4 (using the specified store) */ + function getV4( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (TestTypeLibrary.TestTypeUint128 v4) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 3, _fieldLayout); + return TestTypeLibrary.TestTypeUint128.wrap(uint128(bytes16(_blob))); + } + + /** Set v4 */ + function setV4( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeUint128 v4 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setStaticField( + _tableId, + _keyTuple, + 3, + abi.encodePacked(TestTypeLibrary.TestTypeUint128.unwrap(v4)), + _fieldLayout + ); + } + + /** Set v4 */ + function _setV4( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeUint128 v4 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setStaticField( + _tableId, + _keyTuple, + 3, + abi.encodePacked(TestTypeLibrary.TestTypeUint128.unwrap(v4)), + _fieldLayout + ); + } + + /** Set v4 (using the specified store) */ + function setV4( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeLibrary.TestTypeUint128 v4 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setStaticField( + _tableId, + _keyTuple, + 3, + abi.encodePacked(TestTypeLibrary.TestTypeUint128.unwrap(v4)), + _fieldLayout + ); + } + + /** Get v5 */ + function getV5( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (ResourceId v5) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 4, _fieldLayout); + return ResourceId.wrap(bytes32(_blob)); + } + + /** Get v5 */ + function _getV5( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (ResourceId v5) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 4, _fieldLayout); + return ResourceId.wrap(bytes32(_blob)); + } + + /** Get v5 (using the specified store) */ + function getV5( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (ResourceId v5) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 4, _fieldLayout); + return ResourceId.wrap(bytes32(_blob)); + } + + /** Set v5 */ + function setV5( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + ResourceId v5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setStaticField(_tableId, _keyTuple, 4, abi.encodePacked(ResourceId.unwrap(v5)), _fieldLayout); + } + + /** Set v5 */ + function _setV5( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + ResourceId v5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setStaticField(_tableId, _keyTuple, 4, abi.encodePacked(ResourceId.unwrap(v5)), _fieldLayout); + } + + /** Set v5 (using the specified store) */ + function setV5( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + ResourceId v5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setStaticField(_tableId, _keyTuple, 4, abi.encodePacked(ResourceId.unwrap(v5)), _fieldLayout); + } + + /** Get the full data */ + function get( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (UserTypedData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + (bytes memory _staticData, PackedCounter _encodedLengths, bytes memory _dynamicData) = StoreSwitch.getRecord( + _tableId, + _keyTuple, + _fieldLayout + ); + return decode(_staticData, _encodedLengths, _dynamicData); + } + + /** Get the full data */ + function _get( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (UserTypedData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + (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) */ + function get( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal view returns (UserTypedData memory _table) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + (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 */ + function set( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) internal { + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** Set the full data using individual values */ + function _set( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) internal { + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** Set the full data using individual values (using the specified store) */ + function set( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) internal { + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** Set the full data using the data struct */ + function set( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + UserTypedData memory _table + ) internal { + bytes memory _staticData = encodeStatic(_table.v1, _table.v2, _table.v3, _table.v4, _table.v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** Set the full data using the data struct */ + function _set( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + UserTypedData memory _table + ) internal { + bytes memory _staticData = encodeStatic(_table.v1, _table.v2, _table.v3, _table.v4, _table.v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData, _fieldLayout); + } + + /** Set the full data using the data struct (using the specified store) */ + function set( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5, + UserTypedData memory _table + ) internal { + bytes memory _staticData = encodeStatic(_table.v1, _table.v2, _table.v3, _table.v4, _table.v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.setRecord(_tableId, _keyTuple, _staticData, _encodedLengths, _dynamicData); + } + + /** + * 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 ( + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) + { + v1 = TestTypeAddress.wrap(address(Bytes.slice20(_blob, 0))); + + v2 = TestTypeInt64.wrap(int64(uint64(Bytes.slice8(_blob, 20)))); + + v3 = TestTypeLibrary.TestTypeBool.wrap(_toBool(uint8(Bytes.slice1(_blob, 28)))); + + v4 = TestTypeLibrary.TestTypeUint128.wrap(uint128(Bytes.slice16(_blob, 29))); + + v5 = ResourceId.wrap(Bytes.slice32(_blob, 45)); + } + + /** + * 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 (UserTypedData memory _table) { + (_table.v1, _table.v2, _table.v3, _table.v4, _table.v5) = decodeStatic(_staticData); + } + + /** Delete all data for given keys */ + function deleteRecord( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** Delete all data for given keys */ + function _deleteRecord( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** Delete all data for given keys (using the specified store) */ + function deleteRecord( + IStore _store, + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + _store.deleteRecord(_tableId, _keyTuple); + } + + /** Tightly pack static data using this table's schema */ + function encodeStatic( + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) internal pure returns (bytes memory) { + return abi.encodePacked(v1, v2, v3, v4, v5); + } + + /** Tightly pack full data using this table's field layout */ + function encode( + TestTypeAddress v1, + TestTypeInt64 v2, + TestTypeLibrary.TestTypeBool v3, + TestTypeLibrary.TestTypeUint128 v4, + ResourceId v5 + ) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(v1, v2, v3, v4, v5); + + PackedCounter _encodedLengths; + bytes memory _dynamicData; + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** Encode keys as a bytes32 array using this table's field layout */ + function encodeKeyTuple( + TestTypeAddress k1, + TestTypeInt64 k2, + TestTypeLibrary.TestTypeBool k3, + TestTypeLibrary.TestTypeUint128 k4, + ResourceId k5 + ) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](5); + _keyTuple[0] = bytes32(uint256(uint160(TestTypeAddress.unwrap(k1)))); + _keyTuple[1] = bytes32(uint256(int256(TestTypeInt64.unwrap(k2)))); + _keyTuple[2] = _boolToBytes32(TestTypeLibrary.TestTypeBool.unwrap(k3)); + _keyTuple[3] = bytes32(uint256(TestTypeLibrary.TestTypeUint128.unwrap(k4))); + _keyTuple[4] = ResourceId.unwrap(k5); + + return _keyTuple; + } +} + +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} + +function _boolToBytes32(bool value) pure returns (bytes32 result) { + assembly { + result := value + } +} diff --git a/packages/cli/contracts/src/types.sol b/packages/cli/contracts/src/types.sol new file mode 100644 index 0000000000..636387331e --- /dev/null +++ b/packages/cli/contracts/src/types.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +type TestTypeInt64 is int64; +type TestTypeAddress is address; + +library TestTypeLibrary { + type TestTypeBool is bool; + type TestTypeUint128 is uint128; +} diff --git a/packages/cli/contracts/test/Tablegen.t.sol b/packages/cli/contracts/test/Tablegen.t.sol index 1d1e611ed2..86de2562f3 100644 --- a/packages/cli/contracts/test/Tablegen.t.sol +++ b/packages/cli/contracts/test/Tablegen.t.sol @@ -5,7 +5,9 @@ import "forge-std/Test.sol"; import { StoreMock } from "@latticexyz/store/test/StoreMock.sol"; import { IStoreErrors } from "@latticexyz/store/src/IStoreErrors.sol"; -import { Statics, StaticsData, Dynamics1, Dynamics1Data, Dynamics2, Dynamics2Data, Singleton, Offchain } from "../src/codegen/index.sol"; +import { Statics, StaticsData, Dynamics1, Dynamics1Data, Dynamics2, Dynamics2Data, Singleton, Offchain, UserTyped, UserTypedData } from "../src/codegen/index.sol"; +import { TestTypeAddress, TestTypeInt64, TestTypeLibrary } from "../src/types.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { Enum1, Enum2 } from "../src/codegen/common.sol"; @@ -150,4 +152,21 @@ contract TablegenTest is Test, StoreMock { Offchain.set("key", 123); } + + function testUserTypes() public { + UserTyped.register(); + + TestTypeAddress k1 = TestTypeAddress.wrap(address(123)); + TestTypeInt64 k2 = TestTypeInt64.wrap(-1); + TestTypeLibrary.TestTypeBool k3 = TestTypeLibrary.TestTypeBool.wrap(true); + TestTypeLibrary.TestTypeUint128 k4 = TestTypeLibrary.TestTypeUint128.wrap(123456); + ResourceId k5 = ResourceId.wrap("test"); + + UserTyped.setV1(k1, k2, k3, k4, k5, TestTypeAddress.wrap(address(this))); + assertEq(TestTypeAddress.unwrap(UserTyped.getV1(k1, k2, k3, k4, k5)), address(this)); + + UserTypedData memory data = UserTypedData(k1, k2, k3, k4, k5); + UserTyped.set(k1, k2, k3, k4, k5, data); + assertEq(abi.encode(UserTyped.get(k1, k2, k3, k4, k5)), abi.encode(data)); + } } diff --git a/packages/cli/scripts/generate-test-tables.ts b/packages/cli/scripts/generate-test-tables.ts index 79d9430437..1d14474bd2 100644 --- a/packages/cli/scripts/generate-test-tables.ts +++ b/packages/cli/scripts/generate-test-tables.ts @@ -1,7 +1,7 @@ import path from "path"; import { tablegen } from "@latticexyz/store/codegen"; import { mudConfig } from "@latticexyz/world/register"; -import { getSrcDirectory } from "@latticexyz/common/foundry"; +import { getRemappings, getSrcDirectory } from "@latticexyz/common/foundry"; import { logError } from "../src/utils/errors"; // This config is used only for tests. @@ -59,21 +59,46 @@ try { valueSchema: "uint256", offchainOnly: true, }, + UserTyped: { + keySchema: { + k1: "TestTypeAddress", + k2: "TestTypeInt64", + k3: "TestTypeBool", + k4: "TestTypeUint128", + k5: "ResourceId", + }, + valueSchema: { + v1: "TestTypeAddress", + v2: "TestTypeInt64", + v3: "TestTypeBool", + v4: "TestTypeUint128", + v5: "ResourceId", + }, + }, }, enums: { Enum1: ["E1", "E2", "E3"], Enum2: ["E1"], }, + + userTypes: { + TestTypeAddress: "./contracts/src/types.sol", + TestTypeInt64: "./contracts/src/types.sol", + TestTypeBool: "./contracts/src/types.sol", + TestTypeUint128: "./contracts/src/types.sol", + ResourceId: "@latticexyz/store/src/ResourceId.sol", + }, }); } catch (error: unknown) { logError(error); } const srcDirectory = await getSrcDirectory(); +const remappings = await getRemappings(); if (config !== undefined) { - tablegen(config, path.join(srcDirectory, config.codegenDirectory)); + tablegen(config, path.join(srcDirectory, config.codegenDirectory), remappings); } else { process.exit(1); } diff --git a/packages/cli/src/commands/dev-contracts.ts b/packages/cli/src/commands/dev-contracts.ts index 145e78fd2e..dfbcbf61c2 100644 --- a/packages/cli/src/commands/dev-contracts.ts +++ b/packages/cli/src/commands/dev-contracts.ts @@ -1,5 +1,12 @@ import type { CommandModule } from "yargs"; -import { anvil, forge, getRpcUrl, getScriptDirectory, getSrcDirectory } from "@latticexyz/common/foundry"; +import { + anvil, + forge, + getRemappings, + getRpcUrl, + getScriptDirectory, + getSrcDirectory, +} from "@latticexyz/common/foundry"; import chalk from "chalk"; import chokidar from "chokidar"; import { loadConfig, resolveConfigPath } from "@latticexyz/config/node"; @@ -47,6 +54,7 @@ const commandModule: CommandModule = { const configPath = args.configPath ?? (await resolveConfigPath(args.configPath)); const srcDirectory = await getSrcDirectory(); const scriptDirectory = await getScriptDirectory(); + const remappings = await getRemappings(); const initialConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig; // Initial run of all codegen steps before starting anvil @@ -132,7 +140,7 @@ const commandModule: CommandModule = { console.log(chalk.blue("mud.config.ts changed - regenerating tables and recs types")); // Run tablegen to generate tables based on the config const outPath = path.join(srcDirectory, config.codegenDirectory); - await tablegen(config, outPath); + await tablegen(config, outPath, remappings); } /** Codegen to run if contracts changed */ diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 5bca2b0f3b..e93ba24858 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -3,7 +3,7 @@ import type { CommandModule } from "yargs"; import { loadConfig } from "@latticexyz/config/node"; import { StoreConfig } from "@latticexyz/store"; import { tablegen } from "@latticexyz/store/codegen"; -import { getSrcDirectory } from "@latticexyz/common/foundry"; +import { getRemappings, getSrcDirectory } from "@latticexyz/common/foundry"; type Options = { configPath?: string; @@ -23,8 +23,9 @@ const commandModule: CommandModule = { async handler({ configPath }) { const config = (await loadConfig(configPath)) as StoreConfig; const srcDir = await getSrcDirectory(); + const remappings = await getRemappings(); - await tablegen(config, path.join(srcDir, config.codegenDirectory)); + await tablegen(config, path.join(srcDir, config.codegenDirectory), remappings); process.exit(0); }, diff --git a/packages/cli/src/utils/deploy.ts b/packages/cli/src/utils/deploy.ts index 7b95542de1..b3eb22ab02 100644 --- a/packages/cli/src/utils/deploy.ts +++ b/packages/cli/src/utils/deploy.ts @@ -1,6 +1,7 @@ import chalk from "chalk"; +import path from "path"; import { ethers } from "ethers"; -import { getOutDirectory, cast } from "@latticexyz/common/foundry"; +import { getOutDirectory, cast, getSrcDirectory, getRemappings } from "@latticexyz/common/foundry"; import { StoreConfig } from "@latticexyz/store"; import { WorldConfig, resolveWorldConfig } from "@latticexyz/world"; import { deployWorldContract } from "./world"; @@ -49,6 +50,8 @@ export async function deploy( deployConfig; const resolvedConfig = resolveWorldConfig(mudConfig, existingContractNames); const forgeOutDirectory = await getOutDirectory(profile); + const remappings = await getRemappings(profile); + const outputBaseDirectory = path.join(await getSrcDirectory(profile), mudConfig.codegenDirectory); // Set up signer for deployment const provider = new ethers.providers.StaticJsonRpcProvider(rpc); @@ -154,7 +157,10 @@ export async function deploy( } const tableIds = getTableIds(mudConfig); - const registerTableCalls = Object.values(mudConfig.tables).map((table) => getRegisterTableCallData(table, mudConfig)); + + const registerTableCalls = Object.values(mudConfig.tables).map((table) => + getRegisterTableCallData(table, mudConfig, outputBaseDirectory, remappings) + ); console.log(chalk.blue("Registering tables")); await Promise.all( diff --git a/packages/cli/src/utils/tables/getRegisterTableCallData.ts b/packages/cli/src/utils/tables/getRegisterTableCallData.ts index 218e89e05d..594c9a54b6 100644 --- a/packages/cli/src/utils/tables/getRegisterTableCallData.ts +++ b/packages/cli/src/utils/tables/getRegisterTableCallData.ts @@ -5,13 +5,21 @@ import { resourceIdToHex } from "@latticexyz/common"; import { Table } from "./types"; import { fieldLayoutToHex } from "@latticexyz/protocol-parser"; import { CallData } from "../utils/types"; +import { loadAndExtractUserTypes } from "@latticexyz/common/codegen"; -export function getRegisterTableCallData(table: Table, storeConfig: StoreConfig): CallData { +export function getRegisterTableCallData( + table: Table, + storeConfig: StoreConfig, + outputBaseDirectory: string, + remappings: [string, string][] +): CallData { const { name, valueSchema, keySchema } = table; if (!name) throw Error("Table missing name"); + const solidityUserTypes = loadAndExtractUserTypes(storeConfig.userTypes, outputBaseDirectory, remappings); + const schemaTypes = Object.values(valueSchema).map((abiOrUserType) => { - const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig); + const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig, solidityUserTypes); return schemaType; }); @@ -22,7 +30,7 @@ export function getRegisterTableCallData(table: Table, storeConfig: StoreConfig) }; const keyTypes = Object.values(keySchema).map((abiOrUserType) => { - const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig); + const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig, solidityUserTypes); return schemaType; }); diff --git a/packages/common/src/codegen/utils/extractUserTypes.ts b/packages/common/src/codegen/utils/extractUserTypes.ts new file mode 100644 index 0000000000..31443ea0fd --- /dev/null +++ b/packages/common/src/codegen/utils/extractUserTypes.ts @@ -0,0 +1,57 @@ +import { parse, visit } from "@solidity-parser/parser"; +import { MUDError } from "../../errors"; + +export interface SolidityUserDefinedType { + typeId: string; + internalTypeId: string; + importSymbol: string; + fromPath: string; + isRelativePath: boolean; +} + +/** + * Parse the solidity data to extract user-defined type information. + * @param data contents of a solidity file with the user types declarations + * @param userTypeNames names of the user types to extract + */ +export function extractUserTypes( + data: string, + userTypeNames: string[], + fromPath: string +): Record { + const ast = parse(data); + + const isRelativePath = fromPath.at(0) === "."; + const userDefinedTypes: Record = {}; + + visit(ast, { + TypeDefinition({ name, definition }, parent) { + if (definition.name.includes("fixed")) throw new MUDError(`Fixed point numbers are not supported by MUD`); + if (userTypeNames.includes(name)) { + if (name in userDefinedTypes) { + throw new MUDError(`File has multiple user types with the same name: ${name}`); + } + + if (parent?.type === "ContractDefinition") { + userDefinedTypes[name] = { + typeId: `${parent.name}.${name}`, + internalTypeId: definition.name, + importSymbol: parent.name, + fromPath, + isRelativePath, + }; + } else { + userDefinedTypes[name] = { + typeId: name, + internalTypeId: definition.name, + importSymbol: name, + fromPath, + isRelativePath, + }; + } + } + }, + }); + + return userDefinedTypes; +} diff --git a/packages/common/src/codegen/utils/index.ts b/packages/common/src/codegen/utils/index.ts index 14125c7f56..574bb1fbd4 100644 --- a/packages/common/src/codegen/utils/index.ts +++ b/packages/common/src/codegen/utils/index.ts @@ -1,4 +1,6 @@ export * from "./contractToInterface"; +export * from "./extractUserTypes"; export * from "./format"; export * from "./formatAndWrite"; +export * from "./loadUserTypesFile"; export * from "./posixPath"; diff --git a/packages/common/src/codegen/utils/loadUserTypesFile.ts b/packages/common/src/codegen/utils/loadUserTypesFile.ts new file mode 100644 index 0000000000..2a5394e483 --- /dev/null +++ b/packages/common/src/codegen/utils/loadUserTypesFile.ts @@ -0,0 +1,53 @@ +import { readFileSync } from "fs"; +import path from "path"; +import { SolidityUserDefinedType, extractUserTypes } from "./extractUserTypes"; + +export function loadAndExtractUserTypes( + userTypes: Record, + outputBaseDirectory: string, + remappings: [string, string][] +): Record { + const userTypesPerFile: Record = {}; + for (const [userTypeName, unresolvedFilePath] of Object.entries(userTypes)) { + if (!(unresolvedFilePath in userTypesPerFile)) { + userTypesPerFile[unresolvedFilePath] = []; + } + userTypesPerFile[unresolvedFilePath].push(userTypeName); + } + let extractedUserTypes: Record = {}; + for (const [unresolvedFilePath, userTypeNames] of Object.entries(userTypesPerFile)) { + const { filePath, data } = loadUserTypesFile(outputBaseDirectory, unresolvedFilePath, remappings); + extractedUserTypes = Object.assign(userTypes, extractUserTypes(data, userTypeNames, filePath)); + } + return extractedUserTypes; +} + +function loadUserTypesFile( + outputBaseDirectory: string, + unresolvedFilePath: string, + remappings: [string, string][] +): { + filePath: string; + data: string; +} { + if (unresolvedFilePath.at(0) === ".") { + return { + filePath: path.relative(outputBaseDirectory, unresolvedFilePath), + data: readFileSync(unresolvedFilePath, "utf8"), + }; + } else { + // apply remappings to read the file via node + let remappedFilePath = unresolvedFilePath; + for (const [from, to] of remappings) { + if (remappedFilePath.includes(from)) { + remappedFilePath = remappedFilePath.replace(from, to); + break; + } + } + + return { + filePath: unresolvedFilePath, + data: readFileSync(remappedFilePath, "utf8"), + }; + } +} diff --git a/packages/common/src/foundry/index.ts b/packages/common/src/foundry/index.ts index c46dbd1aef..fa46956b9b 100644 --- a/packages/common/src/foundry/index.ts +++ b/packages/common/src/foundry/index.ts @@ -10,6 +10,9 @@ export interface ForgeConfig { libs: string[]; eth_rpc_url: string | null; + // compiler + remappings: string[]; + // all unspecified keys (this interface is far from comprehensive) [key: string]: unknown; } @@ -67,6 +70,15 @@ export async function getRpcUrl(profile?: string): Promise { return (await getForgeConfig(profile)).eth_rpc_url || "http://127.0.0.1:8545"; } +/** + * Get the value of "remappings" from forge config + * @param profile The foundry profile to use + * @returns The array of remapping tuples `[from, to]` + */ +export async function getRemappings(profile?: string): Promise<[string, string][]> { + return (await getForgeConfig(profile)).remappings.map((line) => line.trim().split("=")) as [string, string][]; +} + /** * Execute a forge command * @param args The arguments to pass to forge diff --git a/packages/store/ts/codegen/renderTable.ts b/packages/store/ts/codegen/renderTable.ts index e941e5c017..a11abddafd 100644 --- a/packages/store/ts/codegen/renderTable.ts +++ b/packages/store/ts/codegen/renderTable.ts @@ -3,7 +3,7 @@ import { renderArguments, renderCommonData, renderList, - renderRelativeImports, + renderImports, renderTableId, renderTypeHelpers, renderWithStore, @@ -56,7 +56,7 @@ export function renderTable(options: RenderTableOptions) { imports.length > 0 ? ` // Import user types - ${renderRelativeImports(imports)} + ${renderImports(imports)} ` : "" } diff --git a/packages/store/ts/codegen/tableOptions.ts b/packages/store/ts/codegen/tableOptions.ts index c1f19d91ce..9e3cff168e 100644 --- a/packages/store/ts/codegen/tableOptions.ts +++ b/packages/store/ts/codegen/tableOptions.ts @@ -1,11 +1,12 @@ import path from "path"; import { SchemaTypeArrayToElement } from "@latticexyz/schema-type/deprecated"; import { - RelativeImportDatum, + ImportDatum, RenderDynamicField, RenderField, RenderKeyTuple, RenderStaticField, + SolidityUserDefinedType, } from "@latticexyz/common/codegen"; import { RenderTableOptions } from "./types"; import { StoreConfig } from "../config"; @@ -17,7 +18,10 @@ export interface TableOptions { renderOptions: RenderTableOptions; } -export function getTableOptions(config: StoreConfig): TableOptions[] { +export function getTableOptions( + config: StoreConfig, + solidityUserTypes: Record +): TableOptions[] { const storeImportPath = config.storeImportPath; const options = []; @@ -31,13 +35,13 @@ export function getTableOptions(config: StoreConfig): TableOptions[] { // field methods can include simply get/set if there's only 1 field and no record methods const withSuffixlessFieldMethods = !withRecordMethods && Object.keys(tableData.valueSchema).length === 1; // list of any symbols that need to be imported - const imports: RelativeImportDatum[] = []; + const imports: ImportDatum[] = []; const keyTuple = Object.keys(tableData.keySchema).map((name) => { const abiOrUserType = tableData.keySchema[name]; - const { renderType } = resolveAbiOrUserType(abiOrUserType, config); + const { renderType } = resolveAbiOrUserType(abiOrUserType, config, solidityUserTypes); - const importDatum = importForAbiOrUserType(abiOrUserType, tableData.directory, config); + const importDatum = importForAbiOrUserType(abiOrUserType, tableData.directory, config, solidityUserTypes); if (importDatum) imports.push(importDatum); if (renderType.isDynamic) throw new Error(`Parsing error: found dynamic key ${name} in table ${tableName}`); @@ -52,9 +56,9 @@ export function getTableOptions(config: StoreConfig): TableOptions[] { const fields = Object.keys(tableData.valueSchema).map((name) => { const abiOrUserType = tableData.valueSchema[name]; - const { renderType, schemaType } = resolveAbiOrUserType(abiOrUserType, config); + const { renderType, schemaType } = resolveAbiOrUserType(abiOrUserType, config, solidityUserTypes); - const importDatum = importForAbiOrUserType(abiOrUserType, tableData.directory, config); + const importDatum = importForAbiOrUserType(abiOrUserType, tableData.directory, config, solidityUserTypes); if (importDatum) imports.push(importDatum); const elementType = SchemaTypeArrayToElement[schemaType]; diff --git a/packages/store/ts/codegen/tablegen.ts b/packages/store/ts/codegen/tablegen.ts index 9852081a41..64c1fd9082 100644 --- a/packages/store/ts/codegen/tablegen.ts +++ b/packages/store/ts/codegen/tablegen.ts @@ -1,5 +1,5 @@ import path from "path"; -import { formatAndWriteSolidity } from "@latticexyz/common/codegen"; +import { formatAndWriteSolidity, loadAndExtractUserTypes } from "@latticexyz/common/codegen"; import { getTableOptions } from "./tableOptions"; import { renderTable } from "./renderTable"; import { renderTypesFromConfig } from "./renderTypesFromConfig"; @@ -7,8 +7,9 @@ import { renderTableIndex } from "./renderTableIndex"; import { rmSync } from "fs"; import { StoreConfig } from "../config"; -export async function tablegen(config: StoreConfig, outputBaseDirectory: string) { - const allTableOptions = getTableOptions(config); +export async function tablegen(config: StoreConfig, outputBaseDirectory: string, remappings: [string, string][]) { + const solidityUserTypes = loadAndExtractUserTypes(config.userTypes, outputBaseDirectory, remappings); + const allTableOptions = getTableOptions(config, solidityUserTypes); const uniqueTableDirectories = new Set(allTableOptions.map(({ outputPath }) => path.dirname(outputPath))); for (const tableDir of uniqueTableDirectories) { diff --git a/packages/store/ts/codegen/types.ts b/packages/store/ts/codegen/types.ts index 96ea355f53..5238965664 100644 --- a/packages/store/ts/codegen/types.ts +++ b/packages/store/ts/codegen/types.ts @@ -1,5 +1,5 @@ import { - RelativeImportDatum, + ImportDatum, RenderDynamicField, RenderField, RenderKeyTuple, @@ -9,7 +9,7 @@ import { export interface RenderTableOptions { /** List of symbols to import, and their file paths */ - imports: RelativeImportDatum[]; + imports: ImportDatum[]; /** Name of the library to render. */ libraryName: string; /** Name of the struct to render. If undefined, struct and its methods aren't rendered. */ diff --git a/packages/store/ts/codegen/userType.ts b/packages/store/ts/codegen/userType.ts index f22f2664de..75de3826c6 100644 --- a/packages/store/ts/codegen/userType.ts +++ b/packages/store/ts/codegen/userType.ts @@ -5,7 +5,7 @@ import { SchemaTypeToAbiType, } from "@latticexyz/schema-type/deprecated"; import { parseStaticArray } from "@latticexyz/config"; -import { RelativeImportDatum, RenderType } from "@latticexyz/common/codegen"; +import { ImportDatum, RenderType, SolidityUserDefinedType } from "@latticexyz/common/codegen"; import { StoreConfig } from "../config"; export type UserTypeInfo = ReturnType; @@ -15,7 +15,8 @@ export type UserTypeInfo = ReturnType; */ export function resolveAbiOrUserType( abiOrUserType: string, - config: StoreConfig + config: StoreConfig, + solidityUserTypes: Record ): { schemaType: SchemaType; renderType: RenderType; @@ -38,7 +39,7 @@ export function resolveAbiOrUserType( } } // user types - return getUserTypeInfo(abiOrUserType, config); + return getUserTypeInfo(abiOrUserType, config, solidityUserTypes); } /** @@ -47,8 +48,9 @@ export function resolveAbiOrUserType( export function importForAbiOrUserType( abiOrUserType: string, usedInDirectory: string, - config: StoreConfig -): RelativeImportDatum | undefined { + config: StoreConfig, + solidityUserTypes: Record +): ImportDatum | undefined { // abi types which directly mirror a SchemaType if (abiOrUserType in AbiTypeToSchemaType) { return undefined; @@ -58,7 +60,25 @@ export function importForAbiOrUserType( if (staticArray) { return undefined; } - // user types + // user-defined types in a user-provided file + if (abiOrUserType in solidityUserTypes) { + // these types can have a library name as their import symbol + const solidityUserType = solidityUserTypes[abiOrUserType]; + const symbol = solidityUserType.importSymbol; + if (solidityUserType.isRelativePath) { + return { + symbol, + fromPath: solidityUserType.fromPath, + usedInPath: usedInDirectory, + }; + } else { + return { + symbol, + path: solidityUserType.fromPath, + }; + } + } + // other user types return { symbol: abiOrUserType, fromPath: config.userTypesFilename, @@ -84,7 +104,8 @@ export function getSchemaTypeInfo(schemaType: SchemaType): RenderType { export function getUserTypeInfo( userType: string, - config: StoreConfig + config: StoreConfig, + solidityUserTypes: Record ): { schemaType: SchemaType; renderType: RenderType; @@ -109,6 +130,28 @@ export function getUserTypeInfo( }, }; } + // user-defined types + if (userType in solidityUserTypes) { + if (!(userType in solidityUserTypes)) { + throw new Error(`User type "${userType}" not found in MUD config`); + } + const solidityUserType = solidityUserTypes[userType]; + const typeId = solidityUserType.typeId; + const schemaType = AbiTypeToSchemaType[solidityUserType.internalTypeId]; + return { + schemaType, + renderType: { + typeId, + typeWithLocation: typeId, + enumName: SchemaType[schemaType], + staticByteLength: getStaticByteLength(schemaType), + isDynamic: false, + typeWrap: `${typeId}.wrap`, + typeUnwrap: `${typeId}.unwrap`, + internalTypeId: `${solidityUserType.internalTypeId}`, + }, + }; + } // invalid throw new Error(`User type "${userType}" does not exist`); } diff --git a/packages/store/ts/config/defaults.ts b/packages/store/ts/config/defaults.ts index be7dfd58e7..c7360eeb39 100644 --- a/packages/store/ts/config/defaults.ts +++ b/packages/store/ts/config/defaults.ts @@ -8,6 +8,7 @@ export const PATH_DEFAULTS = { export const DEFAULTS = { namespace: "", enums: {} as Record, + userTypes: {} as Record, } as const; export const TABLE_DEFAULTS = { diff --git a/packages/store/ts/config/storeConfig.test-d.ts b/packages/store/ts/config/storeConfig.test-d.ts index f31cd59f21..be99051fec 100644 --- a/packages/store/ts/config/storeConfig.test-d.ts +++ b/packages/store/ts/config/storeConfig.test-d.ts @@ -11,5 +11,8 @@ describe("StoreUserConfig", () => { expectTypeOf[string]>().toEqualTypeOf< NonNullable>["enums"]>[string] >(); + expectTypeOf[string]>().toEqualTypeOf< + NonNullable>["userTypes"]>[string] + >(); // TODO If more nested schemas are added, provide separate tests for them }); diff --git a/packages/store/ts/config/storeConfig.ts b/packages/store/ts/config/storeConfig.ts index dd0627424a..b7df302df9 100644 --- a/packages/store/ts/config/storeConfig.ts +++ b/packages/store/ts/config/storeConfig.ts @@ -27,6 +27,7 @@ const zTableName = zObjectName; const zKeyName = zValueName; const zColumnName = zValueName; const zUserEnumName = zObjectName; +const zUserTypeName = zObjectName; // Fields can use AbiType or one of user-defined wrapper types // (user types are refined later, based on the appropriate config options) @@ -195,7 +196,7 @@ export type ExpandTablesConfig> = { /************************************************************************ * - * USER TYPES + * ENUMS * ************************************************************************/ @@ -234,6 +235,53 @@ export const zEnumsConfig = z.object({ enums: z.record(zUserEnumName, zUserEnum).default(DEFAULTS.enums), }); +/************************************************************************ + * + * USER TYPES + * + ************************************************************************/ + +export type UserTypesConfig = never extends UserTypeNames + ? { + /** + * User types mapped to file paths from which to import them. + * Paths are treated as relative to root. + * Paths that don't start with a "." have foundry remappings applied to them first. + * + * (user types are inferred to be absent) + */ + userTypes?: Record; + } + : StringForUnion extends UserTypeNames + ? { + /** + * User types mapped to file paths from which to import them. + * Paths are treated as relative to root. + * Paths that don't start with a "." have foundry remappings applied to them first. + * + * (user types aren't inferred - use `mudConfig` or `storeConfig` helper, and `as const` for variables) + */ + userTypes?: Record; + } + : { + /** + * User types mapped to file paths from which to import them. + * Paths are treated as relative to root. + * Paths that don't start with a "." have foundry remappings applied to them first. + * + * User types defined here can be used as types in table schemas/keys + */ + userTypes: Record; + }; + +export type FullUserTypesConfig = { + userTypes: Record; +}; + +export const zUserTypesConfig = z.object({ + userTypes: z.record(zUserTypeName, z.string()).default(DEFAULTS.userTypes), +}); + /************************************************************************ * * FINAL @@ -245,9 +293,11 @@ export const zEnumsConfig = z.object({ export type MUDUserConfig< T extends MUDCoreUserConfig = MUDCoreUserConfig, EnumNames extends StringForUnion = StringForUnion, - StaticUserTypes extends ExtractUserTypes = ExtractUserTypes + UserTypeNames extends StringForUnion = StringForUnion, + StaticUserTypes extends ExtractUserTypes = ExtractUserTypes > = T & - EnumsConfig & { + EnumsConfig & + UserTypesConfig & { /** * Configuration for each table. * @@ -279,7 +329,8 @@ const StoreConfigUnrefined = z codegenDirectory: z.string().default(PATH_DEFAULTS.codegenDirectory), codegenIndexFilename: z.string().default(PATH_DEFAULTS.codegenIndexFilename), }) - .merge(zEnumsConfig); + .merge(zEnumsConfig) + .merge(zUserTypesConfig); // finally validate global conditions export const zStoreConfig = StoreConfigUnrefined.superRefine(validateStoreConfig); @@ -312,14 +363,16 @@ function validateStoreConfig(config: z.output, ctx: } // Global names must be unique const tableLibraryNames = Object.keys(config.tables); - const staticUserTypeNames = Object.keys(config.enums); + const staticUserTypeNames = [...Object.keys(config.enums), ...Object.keys(config.userTypes)]; const userTypeNames = staticUserTypeNames; const globalNames = [...tableLibraryNames, ...userTypeNames]; const duplicateGlobalNames = getDuplicates(globalNames); if (duplicateGlobalNames.length > 0) { ctx.addIssue({ code: ZodIssueCode.custom, - message: `Table library names, enum names must be globally unique: ${duplicateGlobalNames.join(", ")}`, + message: `Table library names, enum names, user type names must be globally unique: ${duplicateGlobalNames.join( + ", " + )}`, }); } // Table names used for tableId must be unique diff --git a/packages/store/ts/register/mudConfig.test-d.ts b/packages/store/ts/register/mudConfig.test-d.ts index e310c93804..3252b86fa0 100644 --- a/packages/store/ts/register/mudConfig.test-d.ts +++ b/packages/store/ts/register/mudConfig.test-d.ts @@ -33,6 +33,7 @@ describe("mudConfig", () => { Enum1: ["E1"]; Enum2: ["E1"]; }; + userTypes: Record; tables: { Table1: { keySchema: { diff --git a/packages/store/ts/register/mudConfig.ts b/packages/store/ts/register/mudConfig.ts index afc08f9dd5..907ef5fb1c 100644 --- a/packages/store/ts/register/mudConfig.ts +++ b/packages/store/ts/register/mudConfig.ts @@ -8,8 +8,9 @@ export function mudConfig< T extends MUDCoreUserConfig, // (`never` is overridden by inference, so only the defined enums can be used by default) EnumNames extends StringForUnion = never, - StaticUserTypes extends ExtractUserTypes = ExtractUserTypes ->(config: MUDUserConfig): ExpandMUDUserConfig { + UserTypeNames extends StringForUnion = never, + StaticUserTypes extends ExtractUserTypes = ExtractUserTypes +>(config: MUDUserConfig): ExpandMUDUserConfig { // eslint-disable-next-line @typescript-eslint/no-explicit-any return mudCoreConfig(config) as any; } diff --git a/packages/store/ts/register/typeExtensions.ts b/packages/store/ts/register/typeExtensions.ts index e0bb8d20cb..f425ab4113 100644 --- a/packages/store/ts/register/typeExtensions.ts +++ b/packages/store/ts/register/typeExtensions.ts @@ -25,6 +25,7 @@ export interface ExpandMUDUserConfig T, { enums: typeof DEFAULTS.enums; + userTypes: typeof DEFAULTS.userTypes; namespace: typeof DEFAULTS.namespace; storeImportPath: typeof PATH_DEFAULTS.storeImportPath; userTypesFilename: typeof PATH_DEFAULTS.userTypesFilename; diff --git a/packages/store/ts/scripts/tablegen.ts b/packages/store/ts/scripts/tablegen.ts index 57e6efc9de..6a1b7aaec3 100644 --- a/packages/store/ts/scripts/tablegen.ts +++ b/packages/store/ts/scripts/tablegen.ts @@ -1,10 +1,11 @@ import path from "path"; import { loadConfig } from "@latticexyz/config/node"; -import { getSrcDirectory } from "@latticexyz/common/foundry"; +import { getRemappings, getSrcDirectory } from "@latticexyz/common/foundry"; import { tablegen } from "../codegen"; import { StoreConfig } from ".."; const config = (await loadConfig()) as StoreConfig; const srcDir = await getSrcDirectory(); +const remappings = await getRemappings(); -await tablegen(config, path.join(srcDir, config.codegenDirectory)); +await tablegen(config, path.join(srcDir, config.codegenDirectory), remappings); diff --git a/packages/world/ts/scripts/tablegen.ts b/packages/world/ts/scripts/tablegen.ts index 9c77691154..5a91d6d119 100644 --- a/packages/world/ts/scripts/tablegen.ts +++ b/packages/world/ts/scripts/tablegen.ts @@ -1,10 +1,11 @@ import path from "path"; import { loadConfig } from "@latticexyz/config/node"; -import { getSrcDirectory } from "@latticexyz/common/foundry"; +import { getRemappings, getSrcDirectory } from "@latticexyz/common/foundry"; import { StoreConfig } from "@latticexyz/store"; import { tablegen } from "@latticexyz/store/codegen"; const config = (await loadConfig()) as StoreConfig; const srcDir = await getSrcDirectory(); +const remappings = await getRemappings(); -await tablegen(config, path.join(srcDir, config.codegenDirectory)); +await tablegen(config, path.join(srcDir, config.codegenDirectory), remappings);