diff --git a/.changeset/mean-seals-nail.md b/.changeset/mean-seals-nail.md new file mode 100644 index 0000000000..7ac373d827 --- /dev/null +++ b/.changeset/mean-seals-nail.md @@ -0,0 +1,25 @@ +--- +"@latticexyz/world": patch +"@latticexyz/store": patch +--- + +The `ResourceType` table is removed. +It was previously used to store the resource type for each resource ID in a `World`. This is no longer necessary as the [resource type is now encoded in the resource ID](https://github.com/latticexyz/mud/pull/1544). + +To still be able to determine whether a given resource ID exists, a `ResourceIds` table has been added. +The previous `ResourceType` table was part of `World` and missed tables that were registered directly via `StoreCore.registerTable` instead of via `World.registerTable` (e.g. when a table was registered as part of a root module). +This problem is solved by the new table `ResourceIds` being part of `Store`. + +`StoreCore`'s `hasTable` function was removed in favor of using `ResourceIds.getExists(tableId)` directly. + +```diff +- import { ResourceType } from "@latticexyz/world/src/tables/ResourceType.sol"; +- import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; ++ import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; + +- bool tableExists = StoreCore.hasTable(tableId); ++ bool tableExists = ResourceIds.getExists(tableId); + +- bool systemExists = ResourceType.get(systemId) != Resource.NONE; ++ bool systemExists = ResourceIds.getExists(systemId); +``` diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 022ad9f50b..be4fa7f7a7 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -351,7 +351,7 @@ "file": "test/KeyEncoding.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register KeyEncoding table", - "gasUsed": 720470 + "gasUsed": 720430 }, { "file": "test/Mixed.t.sol", @@ -363,7 +363,7 @@ "file": "test/Mixed.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register Mixed table", - "gasUsed": 582295 + "gasUsed": 582255 }, { "file": "test/Mixed.t.sol", @@ -651,145 +651,145 @@ "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access non-existing record", - "gasUsed": 7142 + "gasUsed": 7042 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access static field of non-existing record", - "gasUsed": 1418 + "gasUsed": 1318 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access dynamic field of non-existing record", - "gasUsed": 2134 + "gasUsed": 2034 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access length of dynamic field of non-existing record", - "gasUsed": 1208 + "gasUsed": 1108 }, { "file": "test/StoreCoreGas.t.sol", "test": "testAccessEmptyData", "name": "access slice of dynamic field of non-existing record", - "gasUsed": 1584 + "gasUsed": 1484 }, { "file": "test/StoreCoreGas.t.sol", "test": "testDeleteData", "name": "delete record (complex data, 3 slots)", - "gasUsed": 6800 + "gasUsed": 6700 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHasFieldLayout", "name": "Check for existence of table (existent)", - "gasUsed": 1349 + "gasUsed": 1249 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHasFieldLayout", "name": "check for existence of table (non-existent)", - "gasUsed": 5349 + "gasUsed": 3249 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "register subscriber", - "gasUsed": 58799 + "gasUsed": 58696 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set record on table with subscriber", - "gasUsed": 71368 + "gasUsed": 71265 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set static field on table with subscriber", - "gasUsed": 20846 + "gasUsed": 20746 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "delete record on table with subscriber", - "gasUsed": 16836 + "gasUsed": 16736 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "register subscriber", - "gasUsed": 58799 + "gasUsed": 58696 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", - "gasUsed": 164486 + "gasUsed": 164386 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", - "gasUsed": 24613 + "gasUsed": 24513 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", - "gasUsed": 17821 + "gasUsed": 17721 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (1 slot, 1 uint32 item)", - "gasUsed": 10364 + "gasUsed": 10264 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (2 slots, 10 uint32 items)", - "gasUsed": 33040 + "gasUsed": 32940 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: register table", - "gasUsed": 644532 + "gasUsed": 642392 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get field layout (warm)", - "gasUsed": 1393 + "gasUsed": 1293 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get value schema (warm)", - "gasUsed": 1904 + "gasUsed": 1804 }, { "file": "test/StoreCoreGas.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "StoreCore: get key schema (warm)", - "gasUsed": 2927 + "gasUsed": 2827 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "set complex record with dynamic data (4 slots)", - "gasUsed": 101940 + "gasUsed": 101840 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "get complex record with dynamic data (4 slots)", - "gasUsed": 4303 + "gasUsed": 4203 }, { "file": "test/StoreCoreGas.t.sol", @@ -807,103 +807,103 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicDataLength", "name": "set dynamic length of dynamic index 0", - "gasUsed": 22970 + "gasUsed": 22870 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicDataLength", "name": "set dynamic length of dynamic index 1", - "gasUsed": 1071 + "gasUsed": 971 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicDataLength", "name": "reduce dynamic length of dynamic index 0", - "gasUsed": 1061 + "gasUsed": 961 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (1 slot)", - "gasUsed": 31701 + "gasUsed": 31601 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get static field (1 slot)", - "gasUsed": 1418 + "gasUsed": 1318 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (overlap 2 slot)", - "gasUsed": 30341 + "gasUsed": 30241 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get static field (overlap 2 slot)", - "gasUsed": 1945 + "gasUsed": 1845 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, first dynamic field)", - "gasUsed": 54066 + "gasUsed": 53966 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, first dynamic field)", - "gasUsed": 2301 + "gasUsed": 2201 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, second dynamic field)", - "gasUsed": 32292 + "gasUsed": 32192 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "get dynamic field (1 slot, second dynamic field)", - "gasUsed": 2304 + "gasUsed": 2204 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "set static record (1 slot)", - "gasUsed": 32248 + "gasUsed": 32145 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "get static record (1 slot)", - "gasUsed": 1624 + "gasUsed": 1524 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "set static record (2 slots)", - "gasUsed": 54753 + "gasUsed": 54650 }, { "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "get static record (2 slots)", - "gasUsed": 1811 + "gasUsed": 1711 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "update in field (1 slot, 1 uint32 item)", - "gasUsed": 9708 + "gasUsed": 9608 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "push to field (2 slots, 6 uint64 items)", - "gasUsed": 10145 + "gasUsed": 10045 }, { "file": "test/StoreHook.t.sol", @@ -1113,7 +1113,7 @@ "file": "test/Vector2.t.sol", "test": "testRegisterAndGetFieldLayout", "name": "register Vector2 field layout", - "gasUsed": 443738 + "gasUsed": 443698 }, { "file": "test/Vector2.t.sol", diff --git a/packages/store/mud.config.ts b/packages/store/mud.config.ts index 814ab527b9..e871948207 100644 --- a/packages/store/mud.config.ts +++ b/packages/store/mud.config.ts @@ -21,6 +21,14 @@ export default mudConfig({ abiEncodedFieldNames: "bytes", }, }, + ResourceIds: { + keySchema: { + resourceId: "bytes32", + }, + valueSchema: { + exists: "bool", + }, + }, // The Hooks table is a generic table used by the `filterFromList` util in `Hook.sol` Hooks: { valueSchema: "bytes21[]", diff --git a/packages/store/src/IStoreErrors.sol b/packages/store/src/IStoreErrors.sol index b4c95bda87..8fb7601964 100644 --- a/packages/store/src/IStoreErrors.sol +++ b/packages/store/src/IStoreErrors.sol @@ -7,6 +7,7 @@ interface IStoreErrors { // Errors include a stringified version of the tableId for easier debugging if cleartext tableIds are used error StoreCore_TableAlreadyExists(ResourceId tableId, string tableIdString); error StoreCore_TableNotFound(ResourceId tableId, string tableIdString); + error StoreCore_InvalidResourceType(string resourceType); error StoreCore_NotImplemented(); error StoreCore_NotDynamicField(); diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol index 62f2c1c4e7..1754771b85 100644 --- a/packages/store/src/StoreCore.sol +++ b/packages/store/src/StoreCore.sol @@ -9,13 +9,14 @@ import { FieldLayout, FieldLayoutLib } from "./FieldLayout.sol"; import { Schema, SchemaLib } from "./Schema.sol"; import { PackedCounter } from "./PackedCounter.sol"; import { Slice, SliceLib } from "./Slice.sol"; -import { StoreHooks, Tables, StoreHooksTableId } from "./codegen/index.sol"; +import { StoreHooks, Tables, ResourceIds, StoreHooksTableId } from "./codegen/index.sol"; import { IStoreErrors } from "./IStoreErrors.sol"; import { IStoreHook } from "./IStoreHook.sol"; import { StoreSwitch } from "./StoreSwitch.sol"; import { Hook, HookLib } from "./Hook.sol"; import { BEFORE_SET_RECORD, AFTER_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD, AFTER_DELETE_RECORD } from "./storeHookTypes.sol"; -import { ResourceId } from "./ResourceId.sol"; +import { ResourceId, ResourceIdInstance } from "./ResourceId.sol"; +import { RESOURCE_TABLE } from "./storeResourceTypes.sol"; /** * StoreCore includes implementations for all IStore methods. @@ -23,6 +24,8 @@ import { ResourceId } from "./ResourceId.sol"; * It's split into a separate library to make it clear that it's not intended to be outside StoreCore. */ library StoreCore { + using ResourceIdInstance for ResourceId; + event HelloStore(bytes32 indexed version); event StoreSetRecord( ResourceId indexed tableId, @@ -77,6 +80,7 @@ library StoreCore { // Register core tables Tables.register(); StoreHooks.register(); + ResourceIds.register(); } /************************************************************************ @@ -101,7 +105,7 @@ library StoreCore { function getKeySchema(ResourceId tableId) internal view returns (Schema keySchema) { keySchema = Schema.wrap(Tables._getKeySchema(ResourceId.unwrap(tableId))); // key schemas can be empty for singleton tables, so we can't depend on key schema for table check - if (!hasTable(tableId)) { + if (!ResourceIds._getExists(ResourceId.unwrap(tableId))) { revert IStoreErrors.StoreCore_TableNotFound(tableId, string(abi.encodePacked(tableId))); } } @@ -116,13 +120,6 @@ library StoreCore { } } - /** - * Check if a table with the given tableId exists - */ - function hasTable(ResourceId tableId) internal view returns (bool) { - return Tables._getFieldLayout(ResourceId.unwrap(tableId)) != bytes32(0); - } - /** * Register a new table the given config */ @@ -134,6 +131,11 @@ library StoreCore { string[] memory keyNames, string[] memory fieldNames ) internal { + // Verify the table ID is of type RESOURCE_TABLE + if (!tableId.isType(RESOURCE_TABLE)) { + revert IStoreErrors.StoreCore_InvalidResourceType(string(bytes.concat(tableId.getType()))); + } + // Verify the field layout is valid fieldLayout.validate({ allowEmpty: false }); @@ -156,8 +158,8 @@ library StoreCore { revert IStoreErrors.StoreCore_InvalidValueSchemaLength(fieldLayout.numFields(), valueSchema.numFields()); } - // Verify the field layout doesn't exist yet - if (hasTable(tableId)) { + // Verify there is no resource with this ID yet + if (ResourceIds._getExists(ResourceId.unwrap(tableId))) { revert IStoreErrors.StoreCore_TableAlreadyExists(tableId, string(abi.encodePacked(tableId))); } @@ -170,6 +172,9 @@ library StoreCore { abi.encode(keyNames), abi.encode(fieldNames) ); + + // Register the table ID + ResourceIds._setExists(ResourceId.unwrap(tableId), true); } /************************************************************************ diff --git a/packages/store/src/codegen/index.sol b/packages/store/src/codegen/index.sol index 64381605a6..7e033ae735 100644 --- a/packages/store/src/codegen/index.sol +++ b/packages/store/src/codegen/index.sol @@ -6,6 +6,7 @@ pragma solidity >=0.8.0; import { StoreHooks, StoreHooksTableId } from "./tables/StoreHooks.sol"; import { Callbacks, CallbacksTableId } from "./tables/Callbacks.sol"; import { Tables, TablesData, TablesTableId } from "./tables/Tables.sol"; +import { ResourceIds, ResourceIdsTableId } from "./tables/ResourceIds.sol"; import { Hooks } from "./tables/Hooks.sol"; import { Mixed, MixedData, MixedTableId } from "./tables/Mixed.sol"; import { Vector2, Vector2Data, Vector2TableId } from "./tables/Vector2.sol"; diff --git a/packages/world/src/modules/core/tables/ResourceType.sol b/packages/store/src/codegen/tables/ResourceIds.sol similarity index 50% rename from packages/world/src/modules/core/tables/ResourceType.sol rename to packages/store/src/codegen/tables/ResourceIds.sol index 063058e35c..316f0ed80c 100644 --- a/packages/world/src/modules/core/tables/ResourceType.sol +++ b/packages/store/src/codegen/tables/ResourceIds.sol @@ -7,32 +7,29 @@ pragma solidity >=0.8.0; 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 } from "@latticexyz/store/src/storeResourceTypes.sol"; - -// Import user types -import { Resource } from "./../../../common.sol"; +import { IStore } from "../../IStore.sol"; +import { StoreSwitch } from "../../StoreSwitch.sol"; +import { StoreCore } from "../../StoreCore.sol"; +import { Bytes } from "../../Bytes.sol"; +import { Memory } from "../../Memory.sol"; +import { SliceLib } from "../../Slice.sol"; +import { EncodeArray } from "../../tightcoder/EncodeArray.sol"; +import { FieldLayout, FieldLayoutLib } from "../../FieldLayout.sol"; +import { Schema, SchemaLib } from "../../Schema.sol"; +import { PackedCounter, PackedCounterLib } from "../../PackedCounter.sol"; +import { ResourceId } from "../../ResourceId.sol"; +import { RESOURCE_TABLE } from "../../storeResourceTypes.sol"; ResourceId constant _tableId = ResourceId.wrap( - bytes32(abi.encodePacked(bytes14(""), bytes16("ResourceType"), RESOURCE_TABLE)) + bytes32(abi.encodePacked(bytes14("mudstore"), bytes16("ResourceIds"), RESOURCE_TABLE)) ); -ResourceId constant ResourceTypeTableId = _tableId; +ResourceId constant ResourceIdsTableId = _tableId; FieldLayout constant _fieldLayout = FieldLayout.wrap( 0x0001010001000000000000000000000000000000000000000000000000000000 ); -library ResourceType { +library ResourceIds { /** Get the table values' field layout */ function getFieldLayout() internal pure returns (FieldLayout) { return _fieldLayout; @@ -49,7 +46,7 @@ library ResourceType { /** Get the table's value schema */ function getValueSchema() internal pure returns (Schema) { SchemaType[] memory _valueSchema = new SchemaType[](1); - _valueSchema[0] = SchemaType.UINT8; + _valueSchema[0] = SchemaType.BOOL; return SchemaLib.encode(_valueSchema); } @@ -57,13 +54,13 @@ library ResourceType { /** Get the table's key names */ function getKeyNames() internal pure returns (string[] memory keyNames) { keyNames = new string[](1); - keyNames[0] = "systemId"; + keyNames[0] = "resourceId"; } /** Get the table's field names */ function getFieldNames() internal pure returns (string[] memory fieldNames) { fieldNames = new string[](1); - fieldNames[0] = "resourceType"; + fieldNames[0] = "exists"; } /** Register the table with its config */ @@ -81,116 +78,116 @@ library ResourceType { _store.registerTable(_tableId, _fieldLayout, getKeySchema(), getValueSchema(), getKeyNames(), getFieldNames()); } - /** Get resourceType */ - function getResourceType(bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists */ + function getExists(bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Get resourceType */ - function _getResourceType(bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists */ + function _getExists(bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Get resourceType (using the specified store) */ - function getResourceType(IStore _store, bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists (using the specified store) */ + function getExists(IStore _store, bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Get resourceType */ - function get(bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists */ + function get(bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = StoreSwitch.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Get resourceType */ - function _get(bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists */ + function _get(bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = StoreCore.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Get resourceType (using the specified store) */ - function get(IStore _store, bytes32 systemId) internal view returns (Resource resourceType) { + /** Get exists (using the specified store) */ + function get(IStore _store, bytes32 resourceId) internal view returns (bool exists) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); - return Resource(uint8(bytes1(_blob))); + return (_toBool(uint8(bytes1(_blob)))); } - /** Set resourceType */ - function setResourceType(bytes32 systemId, Resource resourceType) internal { + /** Set exists */ + function setExists(bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } - /** Set resourceType */ - function _setResourceType(bytes32 systemId, Resource resourceType) internal { + /** Set exists */ + function _setExists(bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - StoreCore.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + StoreCore.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } - /** Set resourceType (using the specified store) */ - function setResourceType(IStore _store, bytes32 systemId, Resource resourceType) internal { + /** Set exists (using the specified store) */ + function setExists(IStore _store, bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - _store.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } - /** Set resourceType */ - function set(bytes32 systemId, Resource resourceType) internal { + /** Set exists */ + function set(bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + StoreSwitch.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } - /** Set resourceType */ - function _set(bytes32 systemId, Resource resourceType) internal { + /** Set exists */ + function _set(bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - StoreCore.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + StoreCore.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } - /** Set resourceType (using the specified store) */ - function set(IStore _store, bytes32 systemId, Resource resourceType) internal { + /** Set exists (using the specified store) */ + function set(IStore _store, bytes32 resourceId, bool exists) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; - _store.setField(_tableId, _keyTuple, 0, abi.encodePacked(uint8(resourceType)), _fieldLayout); + _store.setField(_tableId, _keyTuple, 0, abi.encodePacked((exists)), _fieldLayout); } /** Tightly pack static data using this table's schema */ - function encodeStatic(Resource resourceType) internal pure returns (bytes memory) { - return abi.encodePacked(resourceType); + function encodeStatic(bool exists) internal pure returns (bytes memory) { + return abi.encodePacked(exists); } /** Tightly pack full data using this table's field layout */ - function encode(Resource resourceType) internal pure returns (bytes memory, PackedCounter, bytes memory) { - bytes memory _staticData = encodeStatic(resourceType); + function encode(bool exists) internal pure returns (bytes memory, PackedCounter, bytes memory) { + bytes memory _staticData = encodeStatic(exists); PackedCounter _encodedLengths; bytes memory _dynamicData; @@ -199,34 +196,40 @@ library ResourceType { } /** Encode keys as a bytes32 array using this table's field layout */ - function encodeKeyTuple(bytes32 systemId) internal pure returns (bytes32[] memory) { + function encodeKeyTuple(bytes32 resourceId) internal pure returns (bytes32[] memory) { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; return _keyTuple; } /* Delete all data for given keys */ - function deleteRecord(bytes32 systemId) internal { + function deleteRecord(bytes32 resourceId) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; StoreSwitch.deleteRecord(_tableId, _keyTuple, _fieldLayout); } /* Delete all data for given keys */ - function _deleteRecord(bytes32 systemId) internal { + function _deleteRecord(bytes32 resourceId) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); } /* Delete all data for given keys (using the specified store) */ - function deleteRecord(IStore _store, bytes32 systemId) internal { + function deleteRecord(IStore _store, bytes32 resourceId) internal { bytes32[] memory _keyTuple = new bytes32[](1); - _keyTuple[0] = systemId; + _keyTuple[0] = resourceId; _store.deleteRecord(_tableId, _keyTuple, _fieldLayout); } } + +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol index 24a2fc348d..75fc9e759f 100644 --- a/packages/store/test/StoreCore.t.sol +++ b/packages/store/test/StoreCore.t.sol @@ -7,7 +7,7 @@ import { StoreCore, StoreCoreInternal } from "../src/StoreCore.sol"; import { Bytes } from "../src/Bytes.sol"; import { SliceLib } from "../src/Slice.sol"; import { EncodeArray } from "../src/tightcoder/EncodeArray.sol"; -import { FieldLayout } from "../src/FieldLayout.sol"; +import { FieldLayout, FieldLayoutLib } from "../src/FieldLayout.sol"; import { Schema } from "../src/Schema.sol"; import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol"; import { StoreMock } from "../test/StoreMock.sol"; @@ -15,8 +15,9 @@ import { IStoreErrors } from "../src/IStoreErrors.sol"; import { IStore } from "../src/IStore.sol"; import { StoreSwitch } from "../src/StoreSwitch.sol"; import { IStoreHook } from "../src/IStoreHook.sol"; -import { Tables, TablesTableId } from "../src/codegen/index.sol"; -import { ResourceId, ResourceIdInstance } from "../src/ResourceId.sol"; +import { Tables, ResourceIds, TablesTableId } from "../src/codegen/index.sol"; +import { ResourceId, ResourceIdLib, ResourceIdInstance } from "../src/ResourceId.sol"; +import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol"; import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol"; import { BEFORE_SET_RECORD, AFTER_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD, AFTER_DELETE_RECORD, ALL, BEFORE_ALL, AFTER_ALL } from "../src/storeHookTypes.sol"; import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; @@ -41,8 +42,12 @@ contract StoreCoreTest is Test, StoreMock { mapping(uint256 => bytes) private testMapping; Schema defaultKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32); string[] defaultKeyNames = new string[](1); + ResourceId _tableId = ResourceIdLib.encode("some table", RESOURCE_TABLE); + ResourceId _tableId2 = ResourceIdLib.encode("some other table", RESOURCE_TABLE); + + function testRegisterTable() public { + ResourceId tableId = _tableId; - function testRegisterAndGetFieldLayout() public { FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); Schema keySchema = SchemaEncodeHelper.encode(SchemaType.UINT8, SchemaType.UINT16); Schema valueSchema = SchemaEncodeHelper.encode( @@ -60,8 +65,6 @@ contract StoreCoreTest is Test, StoreMock { fieldNames[2] = "value3"; fieldNames[3] = "value4"; - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); - // Expect a StoreSetRecord event to be emitted bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = ResourceId.unwrap(tableId); @@ -84,13 +87,47 @@ contract StoreCoreTest is Test, StoreMock { bytes memory loadedFieldNames = Tables.getAbiEncodedFieldNames(IStore(this), ResourceId.unwrap(tableId)); assertEq(loadedFieldNames, abi.encode(fieldNames)); + + // Expect the table ID to be registered + assertTrue(ResourceIds._getExists(ResourceId.unwrap(tableId))); + } + + function testRevertTableExists() public { + ResourceId tableId = _tableId; + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 0); + Schema keySchema = SchemaEncodeHelper.encode(SchemaType.UINT8); + Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT8); + string[] memory keyNames = new string[](1); + string[] memory fieldNames = new string[](1); + + IStore(this).registerTable(tableId, fieldLayout, keySchema, valueSchema, keyNames, fieldNames); + + // Expect a revert when registering a table that already exists + vm.expectRevert( + abi.encodeWithSelector( + IStoreErrors.StoreCore_TableAlreadyExists.selector, + ResourceId.unwrap(tableId), + string(bytes.concat(ResourceId.unwrap(tableId))) + ) + ); + IStore(this).registerTable(tableId, fieldLayout, keySchema, valueSchema, keyNames, fieldNames); } - function testFailRegisterInvalidFieldLayout() public { + function testRevertRegisterInvalidFieldLayout() public { + ResourceId tableId = _tableId; + string[] memory keyNames = new string[](2); string[] memory fieldNames = new string[](4); + FieldLayout invalidFieldLayout = FieldLayout.wrap(keccak256("random bytes as value field layout")); + + vm.expectRevert( + abi.encodeWithSelector( + FieldLayoutLib.FieldLayoutLib_InvalidLength.selector, + invalidFieldLayout.numDynamicFields() + ) + ); IStore(this).registerTable( - ResourceId.wrap(keccak256("tableId")), + tableId, FieldLayout.wrap(keccak256("random bytes as value field layout")), Schema.wrap(keccak256("random bytes as key schema")), Schema.wrap(keccak256("random bytes as value schema")), @@ -99,7 +136,27 @@ contract StoreCoreTest is Test, StoreMock { ); } + function testRevertRegisterInvalidTableId() public { + bytes2 invalidType = "xx"; + ResourceId invalidTableId = ResourceIdLib.encode("somename", invalidType); + + vm.expectRevert( + abi.encodeWithSelector(IStoreErrors.StoreCore_InvalidResourceType.selector, string(bytes.concat(invalidType))) + ); + IStore(this).registerTable( + invalidTableId, + FieldLayoutEncodeHelper.encode(1, 0), + SchemaEncodeHelper.encode(SchemaType.UINT8), + SchemaEncodeHelper.encode(SchemaType.UINT8), + new string[](1), + new string[](1) + ); + } + function testHasFieldLayoutAndSchema() public { + ResourceId tableId = _tableId; + ResourceId tableId2 = _tableId2; + string[] memory keyNames = new string[](1); string[] memory fieldNames = new string[](4); FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); @@ -109,12 +166,10 @@ contract StoreCoreTest is Test, StoreMock { SchemaType.UINT8, SchemaType.UINT16 ); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); - ResourceId tableId2 = ResourceId.wrap(keccak256("other.tableId")); IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, keyNames, fieldNames); - assertTrue(StoreCore.hasTable(tableId)); - assertFalse(StoreCore.hasTable(tableId2)); + assertTrue(ResourceIds._getExists(ResourceId.unwrap(tableId))); + assertFalse(ResourceIds._getExists(ResourceId.unwrap(tableId2))); IStore(this).getFieldLayout(tableId); IStore(this).getValueSchema(tableId); @@ -149,7 +204,8 @@ contract StoreCoreTest is Test, StoreMock { } function testRegisterTableRevertNames() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 0); Schema keySchema = SchemaEncodeHelper.encode( SchemaType.UINT8, @@ -171,7 +227,7 @@ contract StoreCoreTest is Test, StoreMock { } function testSetAndGetDynamicDataLength() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 4, 2); Schema valueSchema = SchemaEncodeHelper.encode( @@ -215,6 +271,8 @@ contract StoreCoreTest is Test, StoreMock { } function testSetAndGetStaticData() public { + ResourceId tableId = _tableId; + // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); Schema valueSchema = SchemaEncodeHelper.encode( @@ -224,7 +282,6 @@ contract StoreCoreTest is Test, StoreMock { SchemaType.UINT16 ); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data @@ -251,7 +308,9 @@ contract StoreCoreTest is Test, StoreMock { assertEq(_dynamicData, ""); } - function testFailSetAndGetStaticData() public { + function testRevertSetAndGetStaticData() public { + ResourceId tableId = _tableId; + // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); Schema valueSchema = SchemaEncodeHelper.encode( @@ -260,7 +319,6 @@ contract StoreCoreTest is Test, StoreMock { SchemaType.UINT8, SchemaType.UINT16 ); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data @@ -270,13 +328,15 @@ contract StoreCoreTest is Test, StoreMock { keyTuple[0] = "some key"; // This should fail because the data is not 6 bytes long + vm.expectRevert(abi.encodeWithSelector(IStoreErrors.StoreCore_InvalidStaticDataLength.selector, 6, 4)); IStore(this).setRecord(tableId, keyTuple, staticData, PackedCounter.wrap(bytes32(0)), new bytes(0), fieldLayout); } function testSetAndGetStaticDataSpanningWords() public { + ResourceId tableId = _tableId; + // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 0); - ResourceId tableId = ResourceId.wrap(keccak256("some.table")); { Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT128, SchemaType.UINT256); IStore(this).registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](2)); @@ -310,7 +370,7 @@ contract StoreCoreTest is Test, StoreMock { } function testSetAndGetDynamicData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); @@ -396,8 +456,10 @@ contract StoreCoreTest is Test, StoreMock { } function testSetAndGetField() public { + ResourceId tableId = _tableId; + SetAndGetData memory _data; - _data.tableId = ResourceId.wrap(keccak256("some.tableId")); + _data.tableId = tableId; // Register table _data.fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 2); @@ -611,7 +673,7 @@ contract StoreCoreTest is Test, StoreMock { } function testDeleteData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); @@ -703,20 +765,9 @@ contract StoreCoreTest is Test, StoreMock { } function testPushToField() public { - TestPushToFieldData memory data = TestPushToFieldData( - ResourceId.wrap(bytes32(0)), - new bytes32[](0), - 0, - "", - "", - "", - "", - "", - "", - "" - ); + ResourceId tableId = _tableId; - data.tableId = ResourceId.wrap(keccak256("some.tableId")); + TestPushToFieldData memory data = TestPushToFieldData(tableId, new bytes32[](0), 0, "", "", "", "", "", "", ""); // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(32, 2); @@ -855,8 +906,10 @@ contract StoreCoreTest is Test, StoreMock { } function testUpdateInField() public { + ResourceId tableId = _tableId; + TestUpdateInFieldData memory data = TestUpdateInFieldData( - ResourceId.wrap(bytes32(0)), + tableId, new bytes32[](0), 0, new uint32[](0), @@ -870,8 +923,6 @@ contract StoreCoreTest is Test, StoreMock { "" ); - data.tableId = ResourceId.wrap(keccak256("some.tableId")); - // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(32, 2); Schema valueSchema = SchemaEncodeHelper.encode( @@ -1001,7 +1052,8 @@ contract StoreCoreTest is Test, StoreMock { } function testAccessEmptyData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(4, 1); Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT32, SchemaType.UINT32_ARRAY); @@ -1028,7 +1080,8 @@ contract StoreCoreTest is Test, StoreMock { } function testRegisterHook() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; @@ -1073,7 +1126,8 @@ contract StoreCoreTest is Test, StoreMock { } function testUnregisterHook() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; @@ -1188,7 +1242,8 @@ contract StoreCoreTest is Test, StoreMock { } function testHooksDynamicData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = "some key"; diff --git a/packages/store/test/StoreCoreDynamic.t.sol b/packages/store/test/StoreCoreDynamic.t.sol index e99b5e6a2d..bd1615b3e3 100644 --- a/packages/store/test/StoreCoreDynamic.t.sol +++ b/packages/store/test/StoreCoreDynamic.t.sol @@ -10,8 +10,9 @@ import { EncodeArray } from "../src/tightcoder/EncodeArray.sol"; import { PackedCounterLib } from "../src/PackedCounter.sol"; import { FieldLayout } from "../src/FieldLayout.sol"; import { Schema } from "../src/Schema.sol"; +import { ResourceId, ResourceIdLib } from "../src/ResourceId.sol"; +import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol"; import { StoreMock } from "../test/StoreMock.sol"; -import { ResourceId } from "../src/ResourceId.sol"; import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol"; import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; @@ -19,7 +20,7 @@ contract StoreCoreDynamicTest is Test, GasReporter, StoreMock { Schema internal defaultKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32); bytes32[] internal _keyTuple; - ResourceId internal _tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId internal _tableId = ResourceIdLib.encode("some table", RESOURCE_TABLE); bytes32 internal firstDataBytes; uint32[] internal secondData; diff --git a/packages/store/test/StoreCoreGas.t.sol b/packages/store/test/StoreCoreGas.t.sol index 08e05b6645..645872210f 100644 --- a/packages/store/test/StoreCoreGas.t.sol +++ b/packages/store/test/StoreCoreGas.t.sol @@ -14,7 +14,9 @@ import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol"; import { StoreMock } from "../test/StoreMock.sol"; import { IStoreErrors } from "../src/IStoreErrors.sol"; import { IStore } from "../src/IStore.sol"; -import { ResourceId } from "../src/ResourceId.sol"; +import { ResourceId, ResourceIdLib } from "../src/ResourceId.sol"; +import { ResourceIds } from "../src/codegen/tables/ResourceIds.sol"; +import { RESOURCE_TABLE } from "../src/storeResourceTypes.sol"; import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol"; import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol"; import { StoreMock } from "./StoreMock.sol"; @@ -33,8 +35,12 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { mapping(uint256 => bytes) private testMapping; Schema defaultKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32); + ResourceId _tableId = ResourceIdLib.encode("some table", RESOURCE_TABLE); + ResourceId _tableId2 = ResourceIdLib.encode("some other table", RESOURCE_TABLE); function testRegisterAndGetFieldLayout() public { + ResourceId tableId = _tableId; + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); Schema valueSchema = SchemaEncodeHelper.encode( SchemaType.UINT8, @@ -43,7 +49,6 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { SchemaType.UINT16 ); Schema keySchema = SchemaEncodeHelper.encode(SchemaType.UINT8, SchemaType.UINT16); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); string[] memory keyNames = new string[](2); keyNames[0] = "key1"; @@ -72,6 +77,9 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testHasFieldLayout() public { + ResourceId tableId = _tableId; + ResourceId tableId2 = _tableId2; + Schema valueSchema = SchemaEncodeHelper.encode( SchemaType.UINT8, SchemaType.UINT16, @@ -79,21 +87,19 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { SchemaType.UINT16 ); FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); - ResourceId tableId2 = ResourceId.wrap(keccak256("other.tableId")); StoreCore.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); startGasReport("Check for existence of table (existent)"); - StoreCore.hasTable(tableId); + ResourceIds._getExists(ResourceId.unwrap(tableId)); endGasReport(); startGasReport("check for existence of table (non-existent)"); - StoreCore.hasTable(tableId2); + ResourceIds._getExists(ResourceId.unwrap(tableId2)); endGasReport(); } function testSetAndGetDynamicDataLength() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; Schema valueSchema = SchemaEncodeHelper.encode( SchemaType.UINT8, @@ -129,6 +135,8 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testSetAndGetStaticData() public { + ResourceId tableId = _tableId; + // Register table Schema valueSchema = SchemaEncodeHelper.encode( SchemaType.UINT8, @@ -137,7 +145,6 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { SchemaType.UINT16 ); FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(1, 2, 1, 2, 0); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); StoreCore.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](4)); // Set data @@ -157,10 +164,11 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testSetAndGetStaticDataSpanningWords() public { + ResourceId tableId = _tableId; + // Register table Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT128, SchemaType.UINT256); FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 0); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); StoreCore.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](2)); // Set data @@ -184,7 +192,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testSetAndGetDynamicData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); @@ -256,7 +264,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testSetAndGetField() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 32, 2); @@ -346,7 +354,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testDeleteData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(16, 2); @@ -399,7 +407,7 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testPushToField() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(32, 2); @@ -485,9 +493,9 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testUpdateInField() public { - TestUpdateInFieldData memory data = TestUpdateInFieldData("", "", "", "", "", "", ""); - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + TestUpdateInFieldData memory data = TestUpdateInFieldData("", "", "", "", "", "", ""); // Register table FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(32, 2); Schema valueSchema = SchemaEncodeHelper.encode( @@ -562,7 +570,8 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testAccessEmptyData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + FieldLayout fieldLayout = FieldLayoutEncodeHelper.encode(4, 1); Schema valueSchema = SchemaEncodeHelper.encode(SchemaType.UINT32, SchemaType.UINT32_ARRAY); @@ -594,7 +603,8 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testHooks() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = keccak256("some key"); @@ -636,7 +646,8 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { } function testHooksDynamicData() public { - ResourceId tableId = ResourceId.wrap(keccak256("some.tableId")); + ResourceId tableId = _tableId; + bytes32[] memory keyTuple = new bytes32[](1); keyTuple[0] = keccak256("some key"); diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 0c3a8486b9..df8301db20 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -3,31 +3,31 @@ "file": "test/AccessControl.t.sol", "test": "testAccessControl", "name": "AccessControl: hasAccess (cold)", - "gasUsed": 6971 + "gasUsed": 7007 }, { "file": "test/AccessControl.t.sol", "test": "testAccessControl", "name": "AccessControl: hasAccess (warm, namespace only)", - "gasUsed": 1549 + "gasUsed": 1567 }, { "file": "test/AccessControl.t.sol", "test": "testAccessControl", "name": "AccessControl: hasAccess (warm)", - "gasUsed": 2979 + "gasUsed": 3015 }, { "file": "test/AccessControl.t.sol", "test": "testRequireAccess", "name": "AccessControl: requireAccess (cold)", - "gasUsed": 7014 + "gasUsed": 7050 }, { "file": "test/AccessControl.t.sol", "test": "testRequireAccess", "name": "AccessControl: requireAccess (warm)", - "gasUsed": 3017 + "gasUsed": 3053 }, { "file": "test/AccessControl.t.sol", @@ -39,67 +39,67 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1414689 + "gasUsed": 1412972 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1414689 + "gasUsed": 1412972 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 157684 + "gasUsed": 157762 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1414689 + "gasUsed": 1412972 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1414689 + "gasUsed": 1412972 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", - "gasUsed": 22094 + "gasUsed": 22112 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 158635 + "gasUsed": 158893 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1414689 + "gasUsed": 1412972 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", - "gasUsed": 20816 + "gasUsed": 20834 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 84623 + "gasUsed": 84761 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 664413 + "gasUsed": 663988 }, { "file": "test/KeysWithValueModule.t.sol", @@ -117,79 +117,79 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 664413 + "gasUsed": 663988 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 134865 + "gasUsed": 134943 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 664413 + "gasUsed": 663988 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 105274 + "gasUsed": 105352 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 34733 + "gasUsed": 34781 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 664413 + "gasUsed": 663988 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 147383 + "gasUsed": 147461 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 112142 + "gasUsed": 112220 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", - "gasUsed": 103814 + "gasUsed": 103811 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", - "gasUsed": 53677 + "gasUsed": 53676 }, { "file": "test/query.t.sol", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", - "gasUsed": 128016 + "gasUsed": 128011 }, { "file": "test/query.t.sol", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", - "gasUsed": 81334 + "gasUsed": 81330 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", - "gasUsed": 83295 + "gasUsed": 83292 }, { "file": "test/query.t.sol", @@ -201,19 +201,19 @@ "file": "test/query.t.sol", "test": "testHasQuery", "name": "HasQuery", - "gasUsed": 18066 + "gasUsed": 18065 }, { "file": "test/query.t.sol", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", - "gasUsed": 5926589 + "gasUsed": 5926588 }, { "file": "test/query.t.sol", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", - "gasUsed": 552783 + "gasUsed": 552782 }, { "file": "test/query.t.sol", @@ -225,7 +225,7 @@ "file": "test/query.t.sol", "test": "testNotValueQuery", "name": "NotValueQuery", - "gasUsed": 47273 + "gasUsed": 47272 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -255,31 +255,31 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 690445 + "gasUsed": 689037 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 51750 + "gasUsed": 51768 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 680717 + "gasUsed": 679315 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 51750 + "gasUsed": 51768 }, { "file": "test/World.t.sol", "test": "testCall", "name": "call a system via the World", - "gasUsed": 12425 + "gasUsed": 12437 }, { "file": "test/World.t.sol", @@ -297,85 +297,91 @@ "file": "test/World.t.sol", "test": "testDeleteRecord", "name": "Delete record", - "gasUsed": 8936 + "gasUsed": 8954 }, { "file": "test/World.t.sol", "test": "testPushToField", "name": "Push data to the table", - "gasUsed": 86698 + "gasUsed": 86716 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 58798 + "gasUsed": 58787 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 52551 + "gasUsed": 52540 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 79392 + "gasUsed": 79381 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 122878 + "gasUsed": 123268 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 74464 + "gasUsed": 74453 + }, + { + "file": "test/World.t.sol", + "test": "testRegisterSystem", + "name": "register a system", + "gasUsed": 165318 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 652093 + "gasUsed": 651688 }, { "file": "test/World.t.sol", "test": "testSetField", "name": "Write data to a table field", - "gasUsed": 37219 + "gasUsed": 37237 }, { "file": "test/World.t.sol", "test": "testSetRecord", "name": "Write data to the table", - "gasUsed": 35213 + "gasUsed": 35231 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (cold)", - "gasUsed": 24449 + "gasUsed": 24467 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (warm)", - "gasUsed": 13595 + "gasUsed": 13613 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (cold)", - "gasUsed": 25060 + "gasUsed": 25078 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (warm)", - "gasUsed": 14265 + "gasUsed": 14283 }, { "file": "test/WorldResourceId.t.sol", diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index 51412869d3..1a77d8309b 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -87,15 +87,6 @@ export default mudConfig({ }, valueSchema: "bytes21[]", }, - ResourceType: { - directory: "modules/core/tables", - keySchema: { - systemId: "bytes32", - }, - valueSchema: { - resourceType: "Resource", - }, - }, FunctionSelectors: { directory: "modules/core/tables", keySchema: { @@ -185,10 +176,6 @@ export default mudConfig({ tableIdArgument: true, }, }, - enums: { - Resource: ["NONE", "NAMESPACE", "TABLE", "SYSTEM"], - }, - excludeSystems: [ // IUniqueEntitySystem is not part of the root namespace and // installed separately by UniqueEntityModule. diff --git a/packages/world/src/common.sol b/packages/world/src/common.sol deleted file mode 100644 index 6c4b0ff193..0000000000 --- a/packages/world/src/common.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/* Autogenerated file. Do not edit manually. */ -enum Resource { - NONE, - NAMESPACE, - TABLE, - SYSTEM -} diff --git a/packages/world/src/index.sol b/packages/world/src/index.sol index 021a7371bf..2e852c4973 100644 --- a/packages/world/src/index.sol +++ b/packages/world/src/index.sol @@ -11,7 +11,6 @@ import { Balances, BalancesTableId } from "./modules/core/tables/Balances.sol"; import { Systems, SystemsTableId } from "./modules/core/tables/Systems.sol"; import { SystemRegistry, SystemRegistryTableId } from "./modules/core/tables/SystemRegistry.sol"; import { SystemHooks, SystemHooksTableId } from "./modules/core/tables/SystemHooks.sol"; -import { ResourceType, ResourceTypeTableId } from "./modules/core/tables/ResourceType.sol"; import { FunctionSelectors, FunctionSelectorsTableId } from "./modules/core/tables/FunctionSelectors.sol"; import { KeysWithValue } from "./modules/keyswithvalue/tables/KeysWithValue.sol"; import { KeysInTable, KeysInTableData, KeysInTableTableId } from "./modules/keysintable/tables/KeysInTable.sol"; diff --git a/packages/world/src/interfaces/IBalanceTransferSystem.sol b/packages/world/src/interfaces/IBalanceTransferSystem.sol index 3660f06395..a00a3bc15c 100644 --- a/packages/world/src/interfaces/IBalanceTransferSystem.sol +++ b/packages/world/src/interfaces/IBalanceTransferSystem.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; /* Autogenerated file. Do not edit manually. */ -import { ResourceId } from "./../WorldResourceId.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; interface IBalanceTransferSystem { function transferBalanceToNamespace(ResourceId fromNamespaceId, ResourceId toNamespaceId, uint256 amount) external; diff --git a/packages/world/src/interfaces/IWorldErrors.sol b/packages/world/src/interfaces/IWorldErrors.sol index fe8b6d526d..7529a6464a 100644 --- a/packages/world/src/interfaces/IWorldErrors.sol +++ b/packages/world/src/interfaces/IWorldErrors.sol @@ -15,4 +15,5 @@ interface IWorldErrors { error DelegationNotFound(address delegator, address delegatee); error InsufficientBalance(uint256 balance, uint256 amount); error InterfaceNotSupported(address contractAddress, bytes4 interfaceId); + error InvalidResourceType(string resourceType); } diff --git a/packages/world/src/interfaces/IWorldRegistrationSystem.sol b/packages/world/src/interfaces/IWorldRegistrationSystem.sol index f7a6d4c61c..46638d3ee6 100644 --- a/packages/world/src/interfaces/IWorldRegistrationSystem.sol +++ b/packages/world/src/interfaces/IWorldRegistrationSystem.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; /* Autogenerated file. Do not edit manually. */ -import { ResourceId } from "./../WorldResourceId.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { ISystemHook } from "./ISystemHook.sol"; import { WorldContextConsumer } from "./../WorldContext.sol"; diff --git a/packages/world/src/modules/core/CoreModule.sol b/packages/world/src/modules/core/CoreModule.sol index 63a4b019c4..8a1125cc36 100644 --- a/packages/world/src/modules/core/CoreModule.sol +++ b/packages/world/src/modules/core/CoreModule.sol @@ -3,13 +3,13 @@ pragma solidity >=0.8.0; import { WorldContextProvider } from "../../WorldContext.sol"; import { ROOT_NAMESPACE, ROOT_NAMESPACE_ID } from "../../constants.sol"; -import { Resource } from "../../common.sol"; import { Module } from "../../Module.sol"; import { IBaseWorld } from "../../interfaces/IBaseWorld.sol"; import { IStoreEphemeral } from "@latticexyz/store/src/IStore.sol"; import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../../WorldResourceId.sol"; import { RESOURCE_SYSTEM } from "../../worldResourceTypes.sol"; @@ -23,7 +23,6 @@ import { CORE_MODULE_NAME, CORE_SYSTEM_NAME } from "./constants.sol"; import { Systems } from "./tables/Systems.sol"; import { FunctionSelectors } from "./tables/FunctionSelectors.sol"; -import { ResourceType } from "./tables/ResourceType.sol"; import { SystemHooks } from "./tables/SystemHooks.sol"; import { SystemRegistry } from "./tables/SystemRegistry.sol"; import { Balances } from "./tables/Balances.sol"; @@ -75,11 +74,10 @@ contract CoreModule is Module { FunctionSelectors.register(); SystemHooks.register(); SystemRegistry.register(); - ResourceType.register(); + ResourceIds._setExists(ResourceId.unwrap(ROOT_NAMESPACE_ID), true); NamespaceOwner._set(ResourceId.unwrap(ROOT_NAMESPACE_ID), _msgSender()); ResourceAccess._set(ResourceId.unwrap(ROOT_NAMESPACE_ID), _msgSender(), true); - ResourceType._set(ResourceId.unwrap(ROOT_NAMESPACE_ID), Resource.NAMESPACE); } /** diff --git a/packages/world/src/modules/core/implementations/BalanceTransferSystem.sol b/packages/world/src/modules/core/implementations/BalanceTransferSystem.sol index 3012389d0d..52cf7d90c1 100644 --- a/packages/world/src/modules/core/implementations/BalanceTransferSystem.sol +++ b/packages/world/src/modules/core/implementations/BalanceTransferSystem.sol @@ -1,15 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; +import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol"; + import { System } from "../../../System.sol"; import { revertWithBytes } from "../../../revertWithBytes.sol"; -import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; +import { WorldResourceIdLib, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; import { AccessControl } from "../../../AccessControl.sol"; +import { RESOURCE_NAMESPACE } from "../../../worldResourceTypes.sol"; import { IWorldErrors } from "../../../interfaces/IWorldErrors.sol"; import { Balances } from "../tables/Balances.sol"; contract BalanceTransferSystem is System, IWorldErrors { + using ResourceIdInstance for ResourceId; using WorldResourceIdInstance for ResourceId; /** @@ -20,6 +24,11 @@ contract BalanceTransferSystem is System, IWorldErrors { ResourceId toNamespaceId, uint256 amount ) public virtual { + // Require the target ID to be a namespace ID + if (!toNamespaceId.isType(RESOURCE_NAMESPACE)) { + revert InvalidResourceType(string(bytes.concat(toNamespaceId.getType()))); + } + // Require caller to have access to the namespace AccessControl.requireAccess(fromNamespaceId, _msgSender()); diff --git a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol index 534f362d47..b86a42fabe 100644 --- a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol @@ -5,10 +5,10 @@ import { IStoreHook, STORE_HOOK_INTERFACE_ID } from "@latticexyz/store/src/IStor import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol"; import { Schema } from "@latticexyz/store/src/Schema.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { System } from "../../../System.sol"; import { ResourceId, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; -import { Resource } from "../../../common.sol"; import { ROOT_NAMESPACE, ROOT_NAME } from "../../../constants.sol"; import { AccessControl } from "../../../AccessControl.sol"; import { requireInterface } from "../../../requireInterface.sol"; @@ -18,7 +18,6 @@ import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol"; import { ResourceAccess } from "../../../tables/ResourceAccess.sol"; import { IWorldErrors } from "../../../interfaces/IWorldErrors.sol"; -import { ResourceType } from "../tables/ResourceType.sol"; import { SystemHooks } from "../tables/SystemHooks.sol"; import { SystemRegistry } from "../tables/SystemRegistry.sol"; import { Systems } from "../tables/Systems.sol"; @@ -50,7 +49,7 @@ contract StoreRegistrationSystem is System, IWorldErrors { // If the namespace doesn't exist yet, register it ResourceId namespaceId = tableId.getNamespaceId(); - if (ResourceType._get(ResourceId.unwrap(namespaceId)) == Resource.NONE) { + if (!ResourceIds._getExists(ResourceId.unwrap(namespaceId))) { // Since this is a root system, we're in the context of the World contract already, // so we can use delegatecall to register the namespace (bool success, bytes memory data) = address(this).delegatecall( @@ -62,14 +61,6 @@ contract StoreRegistrationSystem is System, IWorldErrors { AccessControl.requireOwner(namespaceId, _msgSender()); } - // Require no resource to exist at this selector yet - if (ResourceType._get(ResourceId.unwrap(tableId)) != Resource.NONE) { - revert ResourceExists(tableId, tableId.toString()); - } - - // Store the table resource type - ResourceType._set(ResourceId.unwrap(tableId), Resource.TABLE); - // Register the table StoreCore.registerTable(tableId, fieldLayout, keySchema, valueSchema, keyNames, fieldNames); } diff --git a/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol index 8b8c8a4b23..b0947e6b25 100644 --- a/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol @@ -2,13 +2,15 @@ pragma solidity >=0.8.0; import { Hook, HookLib } from "@latticexyz/store/src/Hook.sol"; +import { ResourceId, ResourceIdInstance } from "@latticexyz/store/src/ResourceId.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { System } from "../../../System.sol"; import { WorldContextConsumer, WORLD_CONTEXT_CONSUMER_INTERFACE_ID } from "../../../WorldContext.sol"; -import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; -import { Resource } from "../../../common.sol"; +import { WorldResourceIdLib, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; import { SystemCall } from "../../../SystemCall.sol"; import { ROOT_NAMESPACE_ID, ROOT_NAME, UNLIMITED_DELEGATION } from "../../../constants.sol"; +import { RESOURCE_NAMESPACE, RESOURCE_SYSTEM } from "../../../worldResourceTypes.sol"; import { AccessControl } from "../../../AccessControl.sol"; import { requireInterface } from "../../../requireInterface.sol"; import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol"; @@ -18,7 +20,6 @@ import { ISystemHook, SYSTEM_HOOK_INTERFACE_ID } from "../../../interfaces/ISyst import { IWorldErrors } from "../../../interfaces/IWorldErrors.sol"; import { IDelegationControl, DELEGATION_CONTROL_INTERFACE_ID } from "../../../interfaces/IDelegationControl.sol"; -import { ResourceType } from "../tables/ResourceType.sol"; import { SystemHooks, SystemHooksTableId } from "../tables/SystemHooks.sol"; import { SystemRegistry } from "../tables/SystemRegistry.sol"; import { Systems } from "../tables/Systems.sol"; @@ -29,18 +30,25 @@ import { FunctionSelectors } from "../tables/FunctionSelectors.sol"; * Registering tables is implemented in StoreRegistrationSystem.sol */ contract WorldRegistrationSystem is System, IWorldErrors { + using ResourceIdInstance for ResourceId; using WorldResourceIdInstance for ResourceId; /** * Register a new namespace */ function registerNamespace(ResourceId namespaceId) public virtual { + // Require the provided namespace ID to have type RESOURCE_NAMESPACE + if (!namespaceId.isType(RESOURCE_NAMESPACE)) { + revert InvalidResourceType(string(bytes.concat(namespaceId.getType()))); + } + // Require namespace to not exist yet - if (ResourceType._get(ResourceId.unwrap(namespaceId)) != Resource.NONE) + if (ResourceIds._getExists(ResourceId.unwrap(namespaceId))) { revert ResourceExists(namespaceId, namespaceId.toString()); + } - // Register namespace resource - ResourceType._set(ResourceId.unwrap(namespaceId), Resource.NAMESPACE); + // Register namespace resource ID + ResourceIds._setExists(ResourceId.unwrap(namespaceId), true); // Register caller as the namespace owner NamespaceOwner._set(ResourceId.unwrap(namespaceId), _msgSender()); @@ -87,6 +95,11 @@ contract WorldRegistrationSystem is System, IWorldErrors { * making it possible to upgrade systems. */ function registerSystem(ResourceId systemId, WorldContextConsumer system, bool publicAccess) public virtual { + // Require the provided system ID to have type RESOURCE_SYSTEM + if (!systemId.isType(RESOURCE_SYSTEM)) { + revert InvalidResourceType(string(bytes.concat(systemId.getType()))); + } + // Require the provided address to implement the WorldContextConsumer interface requireInterface(address(system), WORLD_CONTEXT_CONSUMER_INTERFACE_ID); @@ -100,21 +113,14 @@ contract WorldRegistrationSystem is System, IWorldErrors { } // If the namespace doesn't exist yet, register it - // otherwise require caller to own the namespace ResourceId namespaceId = systemId.getNamespaceId(); - if (ResourceType._get(ResourceId.unwrap(namespaceId)) == Resource.NONE) { + if (!ResourceIds._getExists(ResourceId.unwrap(namespaceId))) { registerNamespace(namespaceId); } else { + // otherwise require caller to own the namespace AccessControl.requireOwner(namespaceId, _msgSender()); } - // TODO: this check is unnecessary with resource types, need to replace with a requirement that the system type is encoded correctly - // Require no resource other than a system to exist at this selector yet - Resource resourceType = ResourceType._get(ResourceId.unwrap(systemId)); - if (resourceType != Resource.NONE && resourceType != Resource.SYSTEM) { - revert ResourceExists(systemId, systemId.toString()); - } - // Check if a system already exists at this system ID address existingSystem = Systems._getSystem(ResourceId.unwrap(systemId)); @@ -126,14 +132,14 @@ contract WorldRegistrationSystem is System, IWorldErrors { // Remove the existing system's access to its namespace ResourceAccess._deleteRecord(ResourceId.unwrap(namespaceId), existingSystem); } else { - // Otherwise, this is a new system, so register its resource type - ResourceType._set(ResourceId.unwrap(systemId), Resource.SYSTEM); + // Otherwise, this is a new system, so register its resource ID + ResourceIds._setExists(ResourceId.unwrap(systemId), true); } - // Systems = mapping from systemId to system address and publicAccess + // Systems = mapping from system ID to system address and public access flag Systems._set(ResourceId.unwrap(systemId), address(system), publicAccess); - // SystemRegistry = mapping from system address to systemId + // SystemRegistry = mapping from system address to system ID SystemRegistry._set(address(system), ResourceId.unwrap(systemId)); // Grant the system access to its namespace diff --git a/packages/world/src/modules/keysintable/KeysInTableModule.sol b/packages/world/src/modules/keysintable/KeysInTableModule.sol index a1acc5fb79..b0357b7dd0 100644 --- a/packages/world/src/modules/keysintable/KeysInTableModule.sol +++ b/packages/world/src/modules/keysintable/KeysInTableModule.sol @@ -2,9 +2,8 @@ pragma solidity >=0.8.0; import { BEFORE_SET_RECORD, AFTER_SPLICE_STATIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD } from "@latticexyz/store/src/storeHookTypes.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; -import { ResourceType } from "../core/tables/ResourceType.sol"; -import { Resource } from "../../common.sol"; import { Module } from "../../Module.sol"; import { IBaseWorld } from "../../interfaces/IBaseWorld.sol"; @@ -47,7 +46,7 @@ contract KeysInTableModule is Module { bool success; bytes memory returnData; - if (ResourceType._get(ResourceId.unwrap(KeysInTableTableId)) == Resource.NONE) { + if (!ResourceIds._getExists(ResourceId.unwrap(KeysInTableTableId))) { // Register the tables (success, returnData) = address(world).delegatecall( abi.encodeCall( diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index f0f82c8860..31d56e6679 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -8,13 +8,14 @@ import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol" import { IStoreHook, STORE_HOOK_INTERFACE_ID } from "@latticexyz/store/src/IStoreHook.sol"; import { StoreCore, StoreCoreInternal } from "@latticexyz/store/src/StoreCore.sol"; +import { IStoreErrors } from "@latticexyz/store/src/IStore.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { FieldLayout, FieldLayoutLib } from "@latticexyz/store/src/FieldLayout.sol"; import { FieldLayoutEncodeHelper } from "@latticexyz/store/test/FieldLayoutEncodeHelper.sol"; import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; import { PackedCounter } from "@latticexyz/store/src/PackedCounter.sol"; import { SchemaEncodeHelper } from "@latticexyz/store/test/SchemaEncodeHelper.sol"; -import { Tables, TablesTableId } from "@latticexyz/store/src/codegen/index.sol"; +import { Tables, ResourceIds, TablesTableId } from "@latticexyz/store/src/codegen/index.sol"; import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; import { ALL, BEFORE_SET_RECORD, AFTER_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD, AFTER_DELETE_RECORD } from "@latticexyz/store/src/storeHookTypes.sol"; import { RevertSubscriber } from "@latticexyz/store/test/RevertSubscriber.sol"; @@ -26,7 +27,6 @@ import { System } from "../src/System.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../src/WorldResourceId.sol"; import { ROOT_NAMESPACE, ROOT_NAME, ROOT_NAMESPACE_ID, UNLIMITED_DELEGATION } from "../src/constants.sol"; import { RESOURCE_TABLE, RESOURCE_SYSTEM, RESOURCE_NAMESPACE } from "../src/worldResourceTypes.sol"; -import { Resource } from "../src/common.sol"; import { WorldContextProvider, WORLD_CONTEXT_CONSUMER_INTERFACE_ID } from "../src/WorldContext.sol"; import { SystemHook } from "../src/SystemHook.sol"; import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "../src/systemHookTypes.sol"; @@ -38,7 +38,6 @@ import { ResourceAccess } from "../src/tables/ResourceAccess.sol"; import { CoreModule } from "../src/modules/core/CoreModule.sol"; import { Systems } from "../src/modules/core/tables/Systems.sol"; import { SystemRegistry } from "../src/modules/core/tables/SystemRegistry.sol"; -import { ResourceType } from "../src/modules/core/tables/ResourceType.sol"; import { IBaseWorld } from "../src/interfaces/IBaseWorld.sol"; import { IWorldErrors } from "../src/interfaces/IWorldErrors.sol"; @@ -315,11 +314,24 @@ contract WorldTest is Test, GasReporter { "caller should have access" ); + // Expect the resource ID to have been registered + assertTrue(ResourceIds.getExists(world, ResourceId.unwrap(namespaceId))); + // Expect an error when registering an existing namespace vm.expectRevert(abi.encodeWithSelector(IWorldErrors.ResourceExists.selector, namespaceId, namespaceId.toString())); world.registerNamespace(namespaceId); } + function testRegisterNamespaceRevertInvalidType() public { + ResourceId invalidNamespaceId = WorldResourceIdLib.encode("namespace", "name", RESOURCE_TABLE); + + // Expect an error when trying to register a namespace with an invalid type + vm.expectRevert( + abi.encodeWithSelector(IWorldErrors.InvalidResourceType.selector, string(bytes.concat(RESOURCE_TABLE))) + ); + world.registerNamespace(invalidNamespaceId); + } + function testTransferNamespace() public { bytes14 namespace = "testTransfer"; ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); @@ -407,7 +419,13 @@ contract WorldTest is Test, GasReporter { assertEq(loadedfieldNames, abi.encode(fieldNames), "value names should be registered"); // Expect an error when registering an existing table - vm.expectRevert(abi.encodeWithSelector(IWorldErrors.ResourceExists.selector, tableId, tableId.toString())); + vm.expectRevert( + abi.encodeWithSelector( + IStoreErrors.StoreCore_TableAlreadyExists.selector, + tableId, + string(bytes.concat(ResourceId.unwrap(tableId))) + ) + ); world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, keyNames, fieldNames); // Expect an error when registering a table in a namespace that is not owned by the caller @@ -427,13 +445,17 @@ contract WorldTest is Test, GasReporter { bytes16 name = "testSystem"; ResourceId systemId = WorldResourceIdLib.encode(namespace, name, RESOURCE_SYSTEM); - // !gasrepot Register a new system + startGasReport("register a system"); world.registerSystem(systemId, system, false); + endGasReport(); // Expect the system to be registered (address registeredAddress, bool publicAccess) = Systems.get(world, ResourceId.unwrap(systemId)); assertEq(registeredAddress, address(system)); + // Expect the system's resource ID to have been registered + assertTrue(ResourceIds.getExists(world, ResourceId.unwrap(systemId))); + // Expect the system namespace to be owned by the caller address routeOwner = NamespaceOwner.get(world, ResourceId.unwrap(namespaceId)); assertEq(routeOwner, address(this)); @@ -480,7 +502,9 @@ contract WorldTest is Test, GasReporter { new string[](1), new string[](1) ); - vm.expectRevert(abi.encodeWithSelector(IWorldErrors.ResourceExists.selector, tableId, tableId.toString())); + vm.expectRevert( + abi.encodeWithSelector(IWorldErrors.InvalidResourceType.selector, string(bytes.concat(RESOURCE_TABLE))) + ); world.registerSystem(tableId, newSystem, true); // Expect an error when registering a system in a namespace that is not owned by the caller @@ -546,15 +570,11 @@ contract WorldTest is Test, GasReporter { "new system should have access to the namespace" ); - // Expect the resource type to still be SYSTEM - assertEq( - uint8(ResourceType.get(world, ResourceId.unwrap(systemId))), - uint8(Resource.SYSTEM), - "resource type should still be SYSTEM" - ); + // Expect the resource ID to still be registered + assertTrue(ResourceIds.getExists(world, ResourceId.unwrap(systemId)), "resource type should still be SYSTEM"); } - function testDuplicateSelectors() public { + function testInvalidIds() public { // Register a new table ResourceId tableId = WorldResourceIdLib.encode("namespace", "name", RESOURCE_TABLE); world.registerTable( @@ -569,16 +589,20 @@ contract WorldTest is Test, GasReporter { // Deploy a new system System system = new System(); - // Expect an error when trying to register a system at the same selector - vm.expectRevert(abi.encodeWithSelector(IWorldErrors.ResourceExists.selector, tableId, tableId.toString())); + // Expect an error when trying to register a system at the same ID + vm.expectRevert( + abi.encodeWithSelector(IWorldErrors.InvalidResourceType.selector, string(bytes.concat(RESOURCE_TABLE))) + ); world.registerSystem(tableId, system, false); // Register a new system ResourceId systemId = WorldResourceIdLib.encode("namespace2", "name", RESOURCE_SYSTEM); world.registerSystem(systemId, new System(), false); - // Expect an error when trying to register a table at the same selector - vm.expectRevert(abi.encodeWithSelector(IWorldErrors.ResourceExists.selector, systemId, systemId.toString())); + // Expect an error when trying to register a table at the same ID + vm.expectRevert( + abi.encodeWithSelector(IStoreErrors.StoreCore_InvalidResourceType.selector, string(bytes.concat(RESOURCE_SYSTEM))) + ); world.registerTable( systemId, Bool.getFieldLayout(), @@ -587,6 +611,23 @@ contract WorldTest is Test, GasReporter { new string[](1), new string[](1) ); + + // Expect an error when trying to register a new table at an existing table ID + vm.expectRevert( + abi.encodeWithSelector( + IStoreErrors.StoreCore_TableAlreadyExists.selector, + ResourceId.unwrap(tableId), + string(bytes.concat(ResourceId.unwrap(tableId))) + ) + ); + world.registerTable( + tableId, + Bool.getFieldLayout(), + defaultKeySchema, + Bool.getValueSchema(), + new string[](1), + new string[](1) + ); } function testGrantAccess() public { @@ -1137,7 +1178,7 @@ contract WorldTest is Test, GasReporter { assertTrue(Bool.get(world, tableId)); } - function testWriteAutonomousSystem() public { + function testWriteNonRootSystem() public { ResourceId tableId = WorldResourceIdLib.encode("namespace", "testTable", RESOURCE_TABLE); // Register a new table world.registerTable( @@ -1150,7 +1191,7 @@ contract WorldTest is Test, GasReporter { ); // Register a new system - ResourceId systemId = WorldResourceIdLib.encode("namespace", "testSystem", RESOURCE_TABLE); + ResourceId systemId = WorldResourceIdLib.encode("namespace", "testSystem", RESOURCE_SYSTEM); WorldTestSystem system = new WorldTestSystem(); world.registerSystem(systemId, system, false); diff --git a/packages/world/test/WorldBalance.t.sol b/packages/world/test/WorldBalance.t.sol index 15e4f1d897..c53e65026a 100644 --- a/packages/world/test/WorldBalance.t.sol +++ b/packages/world/test/WorldBalance.t.sol @@ -231,6 +231,35 @@ contract WorldBalanceTest is Test, GasReporter { assertEq(Balances.get(world, ResourceId.unwrap(namespaceId)), 0); } + function testTransferBalanceToNamespaceRevertInvalidResourceType() public { + uint256 value = 1 ether; + + // Expect the root namespace to have no balance + assertEq(Balances.get(world, ResourceId.unwrap(ROOT_NAMESPACE_ID)), 0); + + // Send balance to root namespace + vm.deal(caller, value); + vm.prank(caller); + (bool success, bytes memory data) = address(world).call{ value: value }(""); + assertTrue(success); + assertEq(data.length, 0); + + // Expect the root namespace to have the value as balance + assertEq(Balances.get(world, ResourceId.unwrap(ROOT_NAMESPACE_ID)), value); + + // Expect revert when attempting to transfer to an invalid namespace + ResourceId invalidNamespace = WorldResourceIdLib.encode("something", "invalid", "xx"); + vm.prank(caller); + vm.expectRevert(abi.encodeWithSelector(IWorldErrors.InvalidResourceType.selector, "xx")); + world.transferBalanceToNamespace(ROOT_NAMESPACE_ID, invalidNamespace, value); + + // Expect the root namespace to have the value as balance + assertEq(Balances.get(world, ResourceId.unwrap(ROOT_NAMESPACE_ID)), value); + + // Expect the non root namespace to have no balance + assertEq(Balances.get(world, ResourceId.unwrap(invalidNamespace)), 0); + } + function testTransferBalanceToAddress() public { uint256 value = 1 ether; diff --git a/packages/world/test/query.t.sol b/packages/world/test/query.t.sol index 1ff7f47b02..589911e44e 100644 --- a/packages/world/test/query.t.sol +++ b/packages/world/test/query.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { Test } from "forge-std/Test.sol"; +import { Test, console } from "forge-std/Test.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; import { FieldLayout } from "@latticexyz/store/src/FieldLayout.sol";