diff --git a/.changeset/modern-brooms-rule.md b/.changeset/modern-brooms-rule.md new file mode 100644 index 0000000000..ecc2abe9f9 --- /dev/null +++ b/.changeset/modern-brooms-rule.md @@ -0,0 +1,25 @@ +--- +"@latticexyz/cli": patch +"@latticexyz/world-modules": patch +"@latticexyz/world": major +--- + +Previously `registerSystem` and `registerTable` had a side effect of registering namespaces if the system or table's namespace didn't exist yet. +This caused a possible frontrunning issue, where an attacker could detect a `registerSystem`/`registerTable` transaction in the mempool, +insert a `registerNamespace` transaction before it, grant themselves access to the namespace, transfer ownership of the namespace to the victim, +so that the `registerSystem`/`registerTable` transactions still went through successfully. +To mitigate this issue, the side effect of registering a namespace in `registerSystem` and `registerTable` has been removed. +Calls to these functions now expect the respective namespace to exist and the caller to own the namespace, otherwise they revert. + +Changes in consuming projects are only necessary if tables or systems are registered manually. +If only the MUD deployer is used to register tables and systems, no changes are necessary, as the MUD deployer has been updated accordingly. + +```diff ++ world.registerNamespace(namespaceId); + world.registerSystem(systemId, system, true); +``` + +```diff ++ world.registerNamespace(namespaceId); + MyTable.register(); +``` diff --git a/examples/minimal/packages/contracts/script/PostDeploy.s.sol b/examples/minimal/packages/contracts/script/PostDeploy.s.sol index 22fd9d5f19..661a15ba47 100644 --- a/examples/minimal/packages/contracts/script/PostDeploy.s.sol +++ b/examples/minimal/packages/contracts/script/PostDeploy.s.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.21; import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { ResourceId, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { MessageTable, MessageTableTableId } from "../src/codegen/index.sol"; @@ -12,6 +12,8 @@ import { IWorld } from "../src/codegen/world/IWorld.sol"; import { ChatNamespacedSystem } from "../src/systems/ChatNamespacedSystem.sol"; contract PostDeploy is Script { + using WorldResourceIdInstance for ResourceId; + function run(address worldAddress) external { // Specify a store so that you can use tables directly in PostDeploy StoreSwitch.setStoreAddress(worldAddress); @@ -29,8 +31,10 @@ contract PostDeploy is Script { namespace: "namespace", name: "ChatNamespaced" }); + IWorld(worldAddress).registerNamespace(systemId.getNamespaceId()); IWorld(worldAddress).registerSystem(systemId, chatNamespacedSystem, true); IWorld(worldAddress).registerFunctionSelector(systemId, "sendMessage(string)"); + // Grant this system access to MessageTable IWorld(worldAddress).grantAccess(MessageTableTableId, address(chatNamespacedSystem)); diff --git a/packages/cli/src/deploy/assertNamespaceOwner.ts b/packages/cli/src/deploy/assertNamespaceOwner.ts deleted file mode 100644 index 4ed1e15c81..0000000000 --- a/packages/cli/src/deploy/assertNamespaceOwner.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Account, Chain, Client, Hex, Transport, getAddress } from "viem"; -import { WorldDeploy, worldTables } from "./common"; -import { hexToResource, resourceToHex } from "@latticexyz/common"; -import { getResourceIds } from "./getResourceIds"; -import { getTableValue } from "./getTableValue"; - -export async function assertNamespaceOwner({ - client, - worldDeploy, - resourceIds, -}: { - readonly client: Client; - readonly worldDeploy: WorldDeploy; - readonly resourceIds: readonly Hex[]; -}): Promise { - const desiredNamespaces = Array.from(new Set(resourceIds.map((resourceId) => hexToResource(resourceId).namespace))); - const existingResourceIds = await getResourceIds({ client, worldDeploy }); - const existingNamespaces = Array.from( - new Set(existingResourceIds.map((resourceId) => hexToResource(resourceId).namespace)) - ); - - const namespaces = desiredNamespaces.filter((namespace) => existingNamespaces.includes(namespace)); - const namespaceOwners = await Promise.all( - namespaces.map(async (namespace) => { - const { owner } = await getTableValue({ - client, - worldDeploy, - table: worldTables.world_NamespaceOwner, - key: { namespaceId: resourceToHex({ type: "namespace", namespace, name: "" }) }, - }); - return [namespace, owner]; - }) - ); - - const unauthorizedNamespaces = namespaceOwners - .filter(([, owner]) => getAddress(owner) !== getAddress(client.account.address)) - .map(([namespace]) => namespace); - - if (unauthorizedNamespaces.length) { - throw new Error(`You are attempting to deploy to namespaces you do not own: ${unauthorizedNamespaces.join(", ")}`); - } -} diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index b75c189ed7..0973392c9b 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -9,12 +9,12 @@ import { getWorldDeploy } from "./getWorldDeploy"; import { ensureFunctions } from "./ensureFunctions"; import { ensureModules } from "./ensureModules"; import { Table } from "./configToTables"; -import { assertNamespaceOwner } from "./assertNamespaceOwner"; +import { ensureNamespaceOwner } from "./ensureNamespaceOwner"; import { debug } from "./debug"; import { resourceLabel } from "./resourceLabel"; import { uniqueBy } from "@latticexyz/common/utils"; import { ensureContractsDeployed } from "./ensureContractsDeployed"; -import { coreModuleBytecode, worldFactoryBytecode, worldFactoryContracts } from "./ensureWorldFactory"; +import { worldFactoryContracts } from "./ensureWorldFactory"; type DeployOptions = { client: Client; @@ -67,12 +67,17 @@ export async function deploy({ throw new Error(`Unsupported World version: ${worldDeploy.worldVersion}`); } - await assertNamespaceOwner({ + const namespaceTxs = await ensureNamespaceOwner({ client, worldDeploy, resourceIds: [...tables.map((table) => table.tableId), ...systems.map((system) => system.systemId)], }); + debug("waiting for all namespace registration transactions to confirm"); + for (const tx of namespaceTxs) { + await waitForTransactionReceipt(client, { hash: tx }); + } + const tableTxs = await ensureTables({ client, worldDeploy, diff --git a/packages/cli/src/deploy/ensureNamespaceOwner.ts b/packages/cli/src/deploy/ensureNamespaceOwner.ts new file mode 100644 index 0000000000..65384d2de3 --- /dev/null +++ b/packages/cli/src/deploy/ensureNamespaceOwner.ts @@ -0,0 +1,71 @@ +import { Account, Chain, Client, Hex, Transport, getAddress } from "viem"; +import { WorldDeploy, worldAbi, worldTables } from "./common"; +import { hexToResource, resourceToHex, writeContract } from "@latticexyz/common"; +import { getResourceIds } from "./getResourceIds"; +import { getTableValue } from "./getTableValue"; +import { debug } from "./debug"; + +export async function ensureNamespaceOwner({ + client, + worldDeploy, + resourceIds, +}: { + readonly client: Client; + readonly worldDeploy: WorldDeploy; + readonly resourceIds: readonly Hex[]; +}): Promise { + const desiredNamespaces = Array.from(new Set(resourceIds.map((resourceId) => hexToResource(resourceId).namespace))); + const existingResourceIds = await getResourceIds({ client, worldDeploy }); + const existingNamespaces = new Set(existingResourceIds.map((resourceId) => hexToResource(resourceId).namespace)); + if (existingNamespaces.size) { + debug( + "found", + existingNamespaces.size, + "existing namespaces:", + Array.from(existingNamespaces) + .map((namespace) => (namespace === "" ? "" : namespace)) + .join(", ") + ); + } + + // Assert ownership of existing namespaces + const existingDesiredNamespaces = desiredNamespaces.filter((namespace) => existingNamespaces.has(namespace)); + const namespaceOwners = await Promise.all( + existingDesiredNamespaces.map(async (namespace) => { + const { owner } = await getTableValue({ + client, + worldDeploy, + table: worldTables.world_NamespaceOwner, + key: { namespaceId: resourceToHex({ type: "namespace", namespace, name: "" }) }, + }); + return [namespace, owner]; + }) + ); + + const unauthorizedNamespaces = namespaceOwners + .filter(([, owner]) => getAddress(owner) !== getAddress(client.account.address)) + .map(([namespace]) => namespace); + + if (unauthorizedNamespaces.length) { + throw new Error(`You are attempting to deploy to namespaces you do not own: ${unauthorizedNamespaces.join(", ")}`); + } + + // Register missing namespaces + const missingNamespaces = desiredNamespaces.filter((namespace) => !existingNamespaces.has(namespace)); + if (missingNamespaces.length > 0) { + debug("registering namespaces", Array.from(missingNamespaces).join(", ")); + } + const registrationTxs = Promise.all( + missingNamespaces.map((namespace) => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + functionName: "registerNamespace", + args: [resourceToHex({ namespace, type: "namespace", name: "" })], + }) + ) + ); + + return registrationTxs; +} diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index 2e39630ee8..87d1a9b1d4 100644 --- a/packages/world-modules/gas-report.json +++ b/packages/world-modules/gas-report.json @@ -75,13 +75,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1413179 + "gasUsed": 1413282 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1413179 + "gasUsed": 1413282 }, { "file": "test/KeysInTableModule.t.sol", @@ -93,13 +93,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1413179 + "gasUsed": 1413282 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1413179 + "gasUsed": 1413282 }, { "file": "test/KeysInTableModule.t.sol", @@ -117,7 +117,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1413179 + "gasUsed": 1413282 }, { "file": "test/KeysInTableModule.t.sol", @@ -135,7 +135,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 653541 + "gasUsed": 668083 }, { "file": "test/KeysWithValueModule.t.sol", @@ -153,7 +153,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 653541 + "gasUsed": 668083 }, { "file": "test/KeysWithValueModule.t.sol", @@ -165,7 +165,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 653541 + "gasUsed": 668083 }, { "file": "test/KeysWithValueModule.t.sol", @@ -183,7 +183,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 653541 + "gasUsed": 668083 }, { "file": "test/KeysWithValueModule.t.sol", @@ -267,7 +267,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromCallboundDelegation", "name": "register a callbound delegation", - "gasUsed": 118325 + "gasUsed": 118319 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -279,7 +279,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromSystemDelegation", "name": "register a systembound delegation", - "gasUsed": 115881 + "gasUsed": 115875 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -291,7 +291,7 @@ "file": "test/StandardDelegationsModule.t.sol", "test": "testCallFromTimeboundDelegation", "name": "register a timebound delegation", - "gasUsed": 112819 + "gasUsed": 112813 }, { "file": "test/StandardDelegationsModule.t.sol", @@ -303,7 +303,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 681852 + "gasUsed": 694780 }, { "file": "test/UniqueEntityModule.t.sol", @@ -315,7 +315,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 648249 + "gasUsed": 663774 }, { "file": "test/UniqueEntityModule.t.sol", diff --git a/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol b/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol index 5a65cd5af5..7a5459f2cb 100644 --- a/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol +++ b/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol @@ -6,6 +6,7 @@ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { Module } from "@latticexyz/world/src/Module.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { Puppet } from "../puppet/Puppet.sol"; import { createPuppet } from "../puppet/createPuppet.sol"; @@ -53,7 +54,10 @@ contract ERC20Module is Module { // Register the ERC20 tables and system IBaseWorld world = IBaseWorld(_world()); - registrationLibrary.delegatecall(abi.encodeCall(ERC20ModuleRegistrationLibrary.register, (world, namespace))); + (bool success, bytes memory returnData) = registrationLibrary.delegatecall( + abi.encodeCall(ERC20ModuleRegistrationLibrary.register, (world, namespace)) + ); + if (!success) revertWithBytes(returnData); // Initialize the Metadata ERC20Metadata.set(_metadataTableId(namespace), metadata); @@ -68,6 +72,7 @@ contract ERC20Module is Module { // Register the ERC20 in the ERC20Registry if (!ResourceIds.getExists(ERC20_REGISTRY_TABLE_ID)) { + world.registerNamespace(MODULE_NAMESPACE_ID); ERC20Registry.register(ERC20_REGISTRY_TABLE_ID); } ERC20Registry.set(ERC20_REGISTRY_TABLE_ID, namespaceId, puppet); @@ -83,6 +88,12 @@ contract ERC20ModuleRegistrationLibrary { * Register systems and tables for a new ERC20 token in a given namespace */ function register(IBaseWorld world, bytes14 namespace) public { + // Register the namespace if it doesn't exist yet + ResourceId tokenNamespace = WorldResourceIdLib.encodeNamespace(namespace); + if (!ResourceIds.getExists(tokenNamespace)) { + world.registerNamespace(tokenNamespace); + } + // Register the tables Allowances.register(_allowancesTableId(namespace)); Balances.register(_balancesTableId(namespace)); diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol index 6a297e05cd..b96caea949 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol @@ -7,6 +7,7 @@ import { Module } from "@latticexyz/world/src/Module.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { InstalledModules } from "@latticexyz/world/src/codegen/tables/InstalledModules.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { Puppet } from "../puppet/Puppet.sol"; import { createPuppet } from "../puppet/createPuppet.sol"; @@ -57,7 +58,10 @@ contract ERC721Module is Module { // Register the ERC721 tables and system IBaseWorld world = IBaseWorld(_world()); - registrationLibrary.delegatecall(abi.encodeCall(ERC721ModuleRegistrationLibrary.register, (world, namespace))); + (bool success, bytes memory returnData) = registrationLibrary.delegatecall( + abi.encodeCall(ERC721ModuleRegistrationLibrary.register, (world, namespace)) + ); + if (!success) revertWithBytes(returnData); // Initialize the Metadata ERC721Metadata.set(_metadataTableId(namespace), metadata); @@ -72,6 +76,7 @@ contract ERC721Module is Module { // Register the ERC721 in the ERC20Registry if (!ResourceIds.getExists(ERC721_REGISTRY_TABLE_ID)) { + world.registerNamespace(MODULE_NAMESPACE_ID); ERC721Registry.register(ERC721_REGISTRY_TABLE_ID); } ERC721Registry.set(ERC721_REGISTRY_TABLE_ID, namespaceId, puppet); @@ -87,8 +92,13 @@ contract ERC721ModuleRegistrationLibrary { * Register systems and tables for a new ERC721 token in a given namespace */ function register(IBaseWorld world, bytes14 namespace) public { - // Register the tables + // Register the namespace if it doesn't exist yet + ResourceId tokenNamespace = WorldResourceIdLib.encodeNamespace(namespace); + if (!ResourceIds.getExists(tokenNamespace)) { + world.registerNamespace(tokenNamespace); + } + // Register the tables OperatorApproval.register(_operatorApprovalTableId(namespace)); Owners.register(_ownersTableId(namespace)); TokenApproval.register(_tokenApprovalTableId(namespace)); diff --git a/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol b/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol index 7ca3362bba..cdfdcab433 100644 --- a/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol +++ b/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol @@ -2,8 +2,9 @@ pragma solidity >=0.8.21; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { BEFORE_SET_RECORD, BEFORE_SPLICE_STATIC_DATA, AFTER_SPLICE_STATIC_DATA, BEFORE_SPLICE_DYNAMIC_DATA, AFTER_SPLICE_DYNAMIC_DATA, BEFORE_DELETE_RECORD } from "@latticexyz/store/src/storeHookTypes.sol"; -import { Module } from "@latticexyz/world/src/Module.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { Module } from "@latticexyz/world/src/Module.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { InstalledModules } from "@latticexyz/world/src/codegen/index.sol"; @@ -45,16 +46,24 @@ contract KeysWithValueModule is Module { // Extract source table id from args ResourceId sourceTableId = ResourceId.wrap(abi.decode(args, (bytes32))); - ResourceId targetTableSelector = getTargetTableId(MODULE_NAMESPACE, sourceTableId); + ResourceId targetTableId = getTargetTableId(MODULE_NAMESPACE, sourceTableId); IBaseWorld world = IBaseWorld(_world()); + // Register the target namespace if it doesn't exist yet + if (!ResourceIds.getExists(targetTableId.getNamespaceId())) { + (bool registrationSuccess, bytes memory registrationReturnData) = address(world).delegatecall( + abi.encodeCall(world.registerNamespace, (targetTableId.getNamespaceId())) + ); + if (!registrationSuccess) revertWithBytes(registrationReturnData); + } + // Register the target table (bool success, bytes memory returnData) = address(world).delegatecall( abi.encodeCall( world.registerTable, ( - targetTableSelector, + targetTableId, KeysWithValue.getFieldLayout(), KeysWithValue.getKeySchema(), KeysWithValue.getValueSchema(), @@ -64,9 +73,11 @@ contract KeysWithValueModule is Module { ) ); + if (!success) revertWithBytes(returnData); + // Grant the hook access to the target table (success, returnData) = address(world).delegatecall( - abi.encodeCall(world.grantAccess, (targetTableSelector, address(hook))) + abi.encodeCall(world.grantAccess, (targetTableId, address(hook))) ); if (!success) revertWithBytes(returnData); diff --git a/packages/world-modules/src/modules/puppet/PuppetModule.sol b/packages/world-modules/src/modules/puppet/PuppetModule.sol index ecf4fcfad4..2b5ec19d9c 100644 --- a/packages/world-modules/src/modules/puppet/PuppetModule.sol +++ b/packages/world-modules/src/modules/puppet/PuppetModule.sol @@ -2,13 +2,15 @@ pragma solidity >=0.8.21; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; - import { Module } from "@latticexyz/world/src/Module.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; +import { WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { PuppetFactorySystem } from "./PuppetFactorySystem.sol"; import { PuppetDelegationControl } from "./PuppetDelegationControl.sol"; -import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_FACTORY, PUPPET_TABLE_ID } from "./constants.sol"; +import { MODULE_NAME, PUPPET_DELEGATION, PUPPET_FACTORY, PUPPET_TABLE_ID, NAMESPACE_ID } from "./constants.sol"; import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; @@ -16,6 +18,8 @@ import { PuppetRegistry } from "./tables/PuppetRegistry.sol"; * This module registers tables and delegation control systems required for puppet delegations */ contract PuppetModule is Module { + using WorldResourceIdInstance for ResourceId; + PuppetDelegationControl private immutable puppetDelegationControl = new PuppetDelegationControl(); PuppetFactorySystem private immutable puppetFactorySystem = new PuppetFactorySystem(); @@ -26,11 +30,23 @@ contract PuppetModule is Module { function installRoot(bytes memory) public { IBaseWorld world = IBaseWorld(_world()); + // Register namespace + (bool success, bytes memory returnData) = address(world).delegatecall( + abi.encodeCall(world.registerNamespace, (NAMESPACE_ID)) + ); + if (!success) revertWithBytes(returnData); + // Register table PuppetRegistry.register(PUPPET_TABLE_ID); - // Register system - (bool success, bytes memory returnData) = address(world).delegatecall( + // Register puppet factory + (success, returnData) = address(world).delegatecall( + abi.encodeCall(world.registerSystem, (PUPPET_FACTORY, puppetFactorySystem, true)) + ); + if (!success) revertWithBytes(returnData); + + // Register puppet delegation control + (success, returnData) = address(world).delegatecall( abi.encodeCall(world.registerSystem, (PUPPET_DELEGATION, puppetDelegationControl, true)) ); if (!success) revertWithBytes(returnData); @@ -39,6 +55,9 @@ contract PuppetModule is Module { function install(bytes memory) public { IBaseWorld world = IBaseWorld(_world()); + // Register namespace + world.registerNamespace(NAMESPACE_ID); + // Register table PuppetRegistry.register(PUPPET_TABLE_ID); diff --git a/packages/world-modules/src/modules/puppet/constants.sol b/packages/world-modules/src/modules/puppet/constants.sol index c441dd03b9..88efb01632 100644 --- a/packages/world-modules/src/modules/puppet/constants.sol +++ b/packages/world-modules/src/modules/puppet/constants.sol @@ -3,12 +3,14 @@ pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; -import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; +import { RESOURCE_SYSTEM, RESOURCE_NAMESPACE } from "@latticexyz/world/src/worldResourceTypes.sol"; import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; bytes16 constant MODULE_NAME = bytes16("puppet"); bytes14 constant NAMESPACE = bytes14("puppet"); +ResourceId constant NAMESPACE_ID = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_NAMESPACE, NAMESPACE))); + ResourceId constant PUPPET_DELEGATION = ResourceId.wrap( bytes32(abi.encodePacked(RESOURCE_SYSTEM, NAMESPACE, bytes16("Delegation"))) ); diff --git a/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol b/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol index 36ddb01e8a..1ba3c0f3f4 100644 --- a/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol +++ b/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol @@ -11,7 +11,7 @@ import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { UniqueEntity } from "./tables/UniqueEntity.sol"; import { UniqueEntitySystem } from "./UniqueEntitySystem.sol"; -import { MODULE_NAME, TABLE_ID, SYSTEM_ID } from "./constants.sol"; +import { MODULE_NAME, TABLE_ID, SYSTEM_ID, NAMESPACE_ID } from "./constants.sol"; /** * This module creates a table that stores a nonce, and @@ -33,11 +33,17 @@ contract UniqueEntityModule is Module { IBaseWorld world = IBaseWorld(_world()); + // Register namespace + (bool success, bytes memory data) = address(world).delegatecall( + abi.encodeCall(world.registerNamespace, (NAMESPACE_ID)) + ); + if (!success) revertWithBytes(data); + // Register table UniqueEntity._register(TABLE_ID); // Register system - (bool success, bytes memory data) = address(world).delegatecall( + (success, data) = address(world).delegatecall( abi.encodeCall(world.registerSystem, (SYSTEM_ID, uniqueEntitySystem, true)) ); if (!success) revertWithBytes(data); @@ -56,6 +62,9 @@ contract UniqueEntityModule is Module { IBaseWorld world = IBaseWorld(_world()); + // Register namespace + world.registerNamespace(NAMESPACE_ID); + // Register table UniqueEntity.register(TABLE_ID); diff --git a/packages/world-modules/src/modules/uniqueentity/constants.sol b/packages/world-modules/src/modules/uniqueentity/constants.sol index 07c4691423..ebefa29b7b 100644 --- a/packages/world-modules/src/modules/uniqueentity/constants.sol +++ b/packages/world-modules/src/modules/uniqueentity/constants.sol @@ -2,12 +2,13 @@ pragma solidity >=0.8.21; import { ResourceId } from "@latticexyz/world/src/WorldResourceId.sol"; -import { RESOURCE_TABLE, RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; +import { RESOURCE_TABLE, RESOURCE_SYSTEM, RESOURCE_NAMESPACE } from "@latticexyz/world/src/worldResourceTypes.sol"; bytes14 constant NAMESPACE = bytes14("uniqueEntity"); bytes16 constant MODULE_NAME = bytes16("uniqueEntity"); bytes16 constant SYSTEM_NAME = bytes16("system"); bytes16 constant TABLE_NAME = bytes16("table"); +ResourceId constant NAMESPACE_ID = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_NAMESPACE, NAMESPACE))); ResourceId constant TABLE_ID = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_TABLE, NAMESPACE, TABLE_NAME))); ResourceId constant SYSTEM_ID = ResourceId.wrap((bytes32(abi.encodePacked(RESOURCE_SYSTEM, NAMESPACE, SYSTEM_NAME)))); diff --git a/packages/world-modules/test/PuppetModule.t.sol b/packages/world-modules/test/PuppetModule.t.sol index 7e29308620..5025d49866 100644 --- a/packages/world-modules/test/PuppetModule.t.sol +++ b/packages/world-modules/test/PuppetModule.t.sol @@ -49,9 +49,25 @@ contract PuppetModuleTest is Test, GasReporter { function setUp() public { world = IBaseWorld(address(new World())); world.initialize(new CoreModule()); + } + + function _setupPuppet() internal { world.installModule(new PuppetModule(), new bytes(0)); + // Register a new namespace and system + world.registerNamespace(systemId.getNamespaceId()); + PuppetTestSystem system = new PuppetTestSystem(); + world.registerSystem(systemId, system, true); + + // Connect the puppet + puppet = PuppetTestSystem(createPuppet(world, systemId)); + } + + function _setupRootPuppet() internal { + world.installRootModule(new PuppetModule(), new bytes(0)); + // Register a new system + world.registerNamespace(systemId.getNamespaceId()); PuppetTestSystem system = new PuppetTestSystem(); world.registerSystem(systemId, system, true); @@ -60,6 +76,8 @@ contract PuppetModuleTest is Test, GasReporter { } function testEmitOnPuppet() public { + _setupPuppet(); + vm.expectEmit(true, true, true, true); emit Hello("hello world"); string memory result = puppet.echoAndEmit("hello world"); @@ -67,6 +85,23 @@ contract PuppetModuleTest is Test, GasReporter { } function testMsgSender() public { + _setupPuppet(); + + assertEq(puppet.msgSender(), address(this)); + } + + function testEmitOnRootPuppet() public { + _setupRootPuppet(); + + vm.expectEmit(true, true, true, true); + emit Hello("hello world"); + string memory result = puppet.echoAndEmit("hello world"); + assertEq(result, "hello world"); + } + + function testMsgSenderRootPuppet() public { + _setupRootPuppet(); + assertEq(puppet.msgSender(), address(this)); } } diff --git a/packages/world-modules/test/StandardDelegationsModule.t.sol b/packages/world-modules/test/StandardDelegationsModule.t.sol index fd7b4a65de..adfc7d613e 100644 --- a/packages/world-modules/test/StandardDelegationsModule.t.sol +++ b/packages/world-modules/test/StandardDelegationsModule.t.sol @@ -25,6 +25,7 @@ import { CALLBOUND_DELEGATION, SYSTEMBOUND_DELEGATION, TIMEBOUND_DELEGATION } fr import { WorldTestSystem, WorldTestSystemReturn } from "@latticexyz/world/test/World.t.sol"; contract StandardDelegationsModuleTest is Test, GasReporter { + using WorldResourceIdInstance for ResourceId; IBaseWorld private world; ResourceId private systemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "namespace", name: "testSystem" }); @@ -38,6 +39,7 @@ contract StandardDelegationsModuleTest is Test, GasReporter { // Register a new system WorldTestSystem system = new WorldTestSystem(); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); } diff --git a/packages/world-modules/test/SystemSwitch.t.sol b/packages/world-modules/test/SystemSwitch.t.sol index cce9879230..6f8cf04f76 100644 --- a/packages/world-modules/test/SystemSwitch.t.sol +++ b/packages/world-modules/test/SystemSwitch.t.sol @@ -8,7 +8,7 @@ import { System } from "@latticexyz/world/src/System.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { World } from "@latticexyz/world/src/World.sol"; import { CoreModule } from "@latticexyz/world/src/modules/core/CoreModule.sol"; -import { ResourceId, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { ROOT_NAMESPACE } from "@latticexyz/world/src/constants.sol"; import { SystemSwitch } from "../src/utils/SystemSwitch.sol"; @@ -38,6 +38,8 @@ contract EchoSystem is System { address constant caller = address(4232); contract SystemSwitchTest is Test, GasReporter { + using WorldResourceIdInstance for ResourceId; + IBaseWorld world; EchoSystem systemA; @@ -68,6 +70,10 @@ contract SystemSwitchTest is Test, GasReporter { rootSystemAId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: ROOT_NAMESPACE, name: "systemA" }); rootSystemBId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: ROOT_NAMESPACE, name: "systemB" }); + // Register namespaces + world.registerNamespace(systemAId.getNamespaceId()); + world.registerNamespace(systemBId.getNamespaceId()); + // Register systems world.registerSystem(systemAId, systemA, true); world.registerSystem(systemBId, systemB, true); diff --git a/packages/world-modules/test/UniqueEntityModule.t.sol b/packages/world-modules/test/UniqueEntityModule.t.sol index 5979a67435..9808e28743 100644 --- a/packages/world-modules/test/UniqueEntityModule.t.sol +++ b/packages/world-modules/test/UniqueEntityModule.t.sol @@ -126,6 +126,7 @@ contract UniqueEntityModuleTest is Test, GasReporter { namespace: "somens", name: "echoUniqueEntity" }); + world.registerNamespace(uniqueEntityTestSystemId.getNamespaceId()); world.registerSystem(uniqueEntityTestSystemId, uniqueEntityTestSystem, true); // Execute `getUniqueEntity` from the context of a World diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 09c68697d8..d1cb3e329a 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -69,7 +69,7 @@ "file": "test/World.t.sol", "test": "testCallFromUnlimitedDelegation", "name": "register an unlimited delegation", - "gasUsed": 47638 + "gasUsed": 47632 }, { "file": "test/World.t.sol", @@ -93,7 +93,7 @@ "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 83149 + "gasUsed": 83138 }, { "file": "test/World.t.sol", @@ -105,19 +105,19 @@ "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 80433 + "gasUsed": 80444 }, { "file": "test/World.t.sol", "test": "testRegisterSystem", "name": "register a system", - "gasUsed": 164283 + "gasUsed": 164317 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 636507 + "gasUsed": 528361 }, { "file": "test/World.t.sol", diff --git a/packages/world/src/AccessControl.sol b/packages/world/src/AccessControl.sol index 988fd1b7f1..0933bec75c 100644 --- a/packages/world/src/AccessControl.sol +++ b/packages/world/src/AccessControl.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; + import { ResourceId, WorldResourceIdInstance } from "./WorldResourceId.sol"; import { IWorldErrors } from "./IWorldErrors.sol"; @@ -50,4 +52,15 @@ library AccessControl { revert IWorldErrors.World_AccessDenied(resourceId.toString(), caller); } } + + /** + * @notice Check for existence of the given resource ID. + * @dev Reverts with IWorldErrors.World_ResourceNotFound if the resource does not exist. + * @param resourceId The resource ID to check existence for. + */ + function requireExistence(ResourceId resourceId) internal view { + if (!ResourceIds._getExists(resourceId)) { + revert IWorldErrors.World_ResourceNotFound(resourceId, resourceId.toString()); + } + } } diff --git a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol index 1df3ca8a96..c380ce0cdd 100644 --- a/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/StoreRegistrationSystem.sol @@ -5,7 +5,6 @@ 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"; @@ -14,8 +13,6 @@ import { AccessControl } from "../../../AccessControl.sol"; import { requireInterface } from "../../../requireInterface.sol"; import { revertWithBytes } from "../../../revertWithBytes.sol"; -import { Systems } from "../../../codegen/tables/Systems.sol"; - import { IWorldErrors } from "../../../IWorldErrors.sol"; import { CORE_SYSTEM_ID } from "../constants.sol"; @@ -49,23 +46,16 @@ contract StoreRegistrationSystem is System, IWorldErrors { string[] calldata fieldNames ) public virtual { // Require the name to not be the namespace's root name - if (tableId.getName() == ROOT_NAME) revert World_InvalidResourceId(tableId, tableId.toString()); - - // If the namespace doesn't exist yet, register it - ResourceId namespaceId = tableId.getNamespaceId(); - if (!ResourceIds._getExists(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 - address coreSystemAddress = Systems._getSystem(CORE_SYSTEM_ID); - (bool success, bytes memory data) = coreSystemAddress.delegatecall( - abi.encodeCall(WorldRegistrationSystem.registerNamespace, (namespaceId)) - ); - if (!success) revertWithBytes(data); - } else { - // otherwise require caller to own the namespace - AccessControl.requireOwner(namespaceId, _msgSender()); + if (tableId.getName() == ROOT_NAME) { + revert World_InvalidResourceId(tableId, tableId.toString()); } + // Require the table's namespace to exist + AccessControl.requireExistence(tableId.getNamespaceId()); + + // Require the caller to own the table's namespace + AccessControl.requireOwner(tableId, _msgSender()); + // 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 21025a33c5..11cd866970 100644 --- a/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/core/implementations/WorldRegistrationSystem.sol @@ -113,6 +113,13 @@ contract WorldRegistrationSystem is System, IWorldErrors { revert World_InvalidResourceType(RESOURCE_SYSTEM, systemId, systemId.toString()); } + // Require the system's namespace to exist + ResourceId namespaceId = systemId.getNamespaceId(); + AccessControl.requireExistence(namespaceId); + + // Require the caller to own the namespace + AccessControl.requireOwner(namespaceId, _msgSender()); + // Require the provided address to implement the WorldContextConsumer interface requireInterface(address(system), WORLD_CONTEXT_CONSUMER_INTERFACE_ID); @@ -127,15 +134,6 @@ contract WorldRegistrationSystem is System, IWorldErrors { revert World_SystemAlreadyExists(address(system)); } - // If the namespace doesn't exist yet, register it - ResourceId namespaceId = systemId.getNamespaceId(); - if (!ResourceIds._getExists(namespaceId)) { - registerNamespace(namespaceId); - } else { - // otherwise require caller to own the namespace - AccessControl.requireOwner(namespaceId, _msgSender()); - } - // Check if a system already exists at this system ID address existingSystem = Systems._getSystem(systemId); diff --git a/packages/world/test/BatchCall.t.sol b/packages/world/test/BatchCall.t.sol index 099f32ae59..e90b461f9f 100644 --- a/packages/world/test/BatchCall.t.sol +++ b/packages/world/test/BatchCall.t.sol @@ -8,7 +8,7 @@ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { World } from "../src/World.sol"; import { System } from "../src/System.sol"; import { UNLIMITED_DELEGATION } from "../src/constants.sol"; -import { ResourceId, WorldResourceIdLib } from "../src/WorldResourceId.sol"; +import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../src/WorldResourceId.sol"; import { RESOURCE_SYSTEM } from "../src/worldResourceTypes.sol"; import { IWorldErrors } from "../src/IWorldErrors.sol"; @@ -47,6 +47,8 @@ contract TestSystem is System { } contract BatchCallTest is Test, GasReporter { + using WorldResourceIdInstance for ResourceId; + IBaseWorld world; bytes14 namespace = "namespace"; bytes16 name = "testSystem"; @@ -56,6 +58,7 @@ contract BatchCallTest is Test, GasReporter { function setUp() public { world = IBaseWorld(address(new World())); world.initialize(new CoreModule()); + world.registerNamespace(systemId.getNamespaceId()); } function testBatchCall() public { diff --git a/packages/world/test/Utils.t.sol b/packages/world/test/Utils.t.sol index b0458c9613..b2660f5210 100644 --- a/packages/world/test/Utils.t.sol +++ b/packages/world/test/Utils.t.sol @@ -9,6 +9,8 @@ import { World } from "../src/World.sol"; import { IBaseWorld } from "../src/codegen/interfaces/IBaseWorld.sol"; import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "../src/WorldResourceId.sol"; import { RESOURCE_SYSTEM } from "../src/worldResourceTypes.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { CoreModule } from "../src/modules/core/CoreModule.sol"; @@ -27,12 +29,17 @@ contract UtilsTest is Test { function setUp() public { world = IBaseWorld(address(new World())); world.initialize(new CoreModule()); + StoreSwitch.setStoreAddress(address(world)); } function _registerAndGetNamespace(bytes14 namespace) internal returns (bytes16 returnedNamespace) { UtilsTestSystem testSystem = new UtilsTestSystem(); bytes16 name = "testSystem"; ResourceId systemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: name }); + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + if (!ResourceIds.getExists(namespaceId)) { + world.registerNamespace(namespaceId); + } world.registerSystem(systemId, testSystem, true); bytes memory data = world.call(systemId, abi.encodeCall(UtilsTestSystem.systemNamespace, ())); diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 678e276c8b..c6651b360e 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -308,6 +308,7 @@ contract WorldTest is Test, GasReporter { name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, false); bytes memory result = world.call(systemId, abi.encodeCall(WorldTestSystem.getStoreAddress, ())); @@ -431,6 +432,7 @@ contract WorldTest is Test, GasReporter { fieldNames[0] = "value1"; fieldNames[1] = "value2"; fieldNames[2] = "value3"; + world.registerNamespace(tableId.getNamespaceId()); startGasReport("Register a new table in the namespace"); world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, keyNames, fieldNames); @@ -460,7 +462,7 @@ contract WorldTest is Test, GasReporter { namespace: namespace, name: "otherTable" }); - _expectAccessDenied(address(0x01), namespace, "", RESOURCE_NAMESPACE); + _expectAccessDenied(address(0x01), namespace, "otherTable", RESOURCE_TABLE); world.registerTable(otherTableId, fieldLayout, defaultKeySchema, valueSchema, keyNames, fieldNames); // Expect the World to not be allowed to call registerTable via an external call @@ -505,15 +507,22 @@ contract WorldTest is Test, GasReporter { // Expect the system to have access to its own namespace assertTrue(ResourceAccess.get({ resourceId: namespaceId, caller: address(system) })); - ResourceId newNamespaceId = WorldResourceIdLib.encodeNamespace("newNamespace"); - // Expect the namespace to be created if it doesn't exist yet - assertEq(NamespaceOwner.get(newNamespaceId), address(0)); - world.registerSystem( - WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "newNamespace", name: "testSystem" }), - new System(), - false + // Expect the registration to fail if the namespace does not exist yet + System newSystem = new System(); + ResourceId invalidNamespaceSystemId = WorldResourceIdLib.encode({ + typeId: RESOURCE_SYSTEM, + namespace: "newNamespace", + name: "testSystem" + }); + assertEq(NamespaceOwner.get(invalidNamespaceSystemId.getNamespaceId()), address(0)); + vm.expectRevert( + abi.encodeWithSelector( + IWorldErrors.World_ResourceNotFound.selector, + invalidNamespaceSystemId.getNamespaceId(), + invalidNamespaceSystemId.getNamespaceId().toString() + ) ); - assertEq(NamespaceOwner.get(newNamespaceId), address(this)); + world.registerSystem(invalidNamespaceSystemId, newSystem, false); // Expect an error when registering an existing system at a new system ID vm.expectRevert(abi.encodeWithSelector(IWorldErrors.World_SystemAlreadyExists.selector, address(system))); @@ -527,7 +536,6 @@ contract WorldTest is Test, GasReporter { world.registerSystem(systemId, system, true); // Expect an error when registering a system at an existing resource ID of a different type - System newSystem = new System(); ResourceId tableId = WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: "", name: "testTable" }); world.registerTable( tableId, @@ -576,7 +584,7 @@ contract WorldTest is Test, GasReporter { ) ); world.registerSystem( - WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "someNamespace", name: "invalidSystem" }), + WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: "", name: "invalidSystem" }), System(address(world)), true ); @@ -591,6 +599,7 @@ contract WorldTest is Test, GasReporter { namespace: namespace, name: systemName }); + world.registerNamespace(systemId.getNamespaceId()); // Register a system System oldSystem = new System(); @@ -631,6 +640,10 @@ contract WorldTest is Test, GasReporter { } function testInvalidIds() public { + // Register the namespaces + world.registerNamespace(WorldResourceIdLib.encodeNamespace("namespace")); + world.registerNamespace(WorldResourceIdLib.encodeNamespace("namespace2")); + // Register a new table ResourceId tableId = WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: "namespace", name: "name" }); world.registerTable( @@ -706,6 +719,7 @@ contract WorldTest is Test, GasReporter { namespace: "testSetRecord", name: "testTable" }); + world.registerNamespace(tableId.getNamespaceId()); // Register a new table world.registerTable( tableId, @@ -741,6 +755,7 @@ contract WorldTest is Test, GasReporter { ResourceId tableId = WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: name }); FieldLayout fieldLayout = Bool.getFieldLayout(); Schema valueSchema = Bool.getValueSchema(); + world.registerNamespace(tableId.getNamespaceId()); // Register a new table world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](1)); @@ -772,6 +787,7 @@ contract WorldTest is Test, GasReporter { Schema valueSchema = AddressArray.getValueSchema(); // Register a new table + world.registerNamespace(tableId.getNamespaceId()); world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](1)); // Create data @@ -817,6 +833,7 @@ contract WorldTest is Test, GasReporter { Schema valueSchema = Bool.getValueSchema(); // Register a new table + world.registerNamespace(tableId.getNamespaceId()); world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](1)); // Write data to the table and expect it to be written @@ -855,6 +872,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, false); // Call a system function without arguments via the World @@ -933,6 +951,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); address caller = address(1); @@ -954,6 +973,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); // Register an unlimited delegation @@ -983,6 +1003,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); // Expect a revert when attempting to perform a call on behalf of an address that doesn't have a delegation @@ -1005,6 +1026,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); // Register a limited delegation @@ -1022,6 +1044,7 @@ contract WorldTest is Test, GasReporter { namespace: "namespace", name: "testSystem" }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); // Register a delegation control mock system @@ -1031,6 +1054,7 @@ contract WorldTest is Test, GasReporter { namespace: "delegation", name: "mock" }); + world.registerNamespace(delegationControlMockId.getNamespaceId()); world.registerSystem(delegationControlMockId, delegationControlMock, true); address delegator = address(1); @@ -1224,6 +1248,7 @@ contract WorldTest is Test, GasReporter { // Register a new system WorldTestSystem system = new WorldTestSystem(); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); // Expect the registration to fail if the contract does not implement the system hook interface @@ -1268,6 +1293,7 @@ contract WorldTest is Test, GasReporter { }); // Register a new system + world.registerNamespace(systemId.getNamespaceId()); WorldTestSystem system = new WorldTestSystem(); world.registerSystem(systemId, system, false); @@ -1309,6 +1335,7 @@ contract WorldTest is Test, GasReporter { name: "testTable" }); // Register a new table + world.registerNamespace(tableId.getNamespaceId()); world.registerTable( tableId, Bool.getFieldLayout(), @@ -1337,6 +1364,7 @@ contract WorldTest is Test, GasReporter { name: "testTable" }); // Register a new table + world.registerNamespace(tableId.getNamespaceId()); world.registerTable( tableId, Bool.getFieldLayout(), @@ -1382,6 +1410,7 @@ contract WorldTest is Test, GasReporter { }); // Register a new non-root system WorldTestSystem system = new WorldTestSystem(); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, false); // Call the sysyem @@ -1397,6 +1426,7 @@ contract WorldTest is Test, GasReporter { // Register a new system WorldTestSystem system = new WorldTestSystem(); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); startGasReport("Register a function selector"); @@ -1428,6 +1458,7 @@ contract WorldTest is Test, GasReporter { // Register a new system WorldTestSystem system = new WorldTestSystem(); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); string memory worldFunc = "testSelector()"; @@ -1495,6 +1526,7 @@ contract WorldTest is Test, GasReporter { bytes16 name = "testSystem"; ResourceId systemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: name }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); world.registerRootFunctionSelector(systemId, "receiveEther()", WorldTestSystem.receiveEther.selector); @@ -1521,6 +1553,7 @@ contract WorldTest is Test, GasReporter { bytes14 namespace = "noroot"; bytes16 name = "testSystem"; ResourceId systemId = WorldResourceIdLib.encode({ typeId: RESOURCE_SYSTEM, namespace: namespace, name: name }); + world.registerNamespace(systemId.getNamespaceId()); world.registerSystem(systemId, system, true); world.registerRootFunctionSelector(systemId, "msgSender()", WorldTestSystem.msgSender.selector); diff --git a/packages/world/test/WorldBalance.t.sol b/packages/world/test/WorldBalance.t.sol index c43c09b0dc..10c44dfd45 100644 --- a/packages/world/test/WorldBalance.t.sol +++ b/packages/world/test/WorldBalance.t.sol @@ -39,6 +39,8 @@ contract WorldBalanceTest is Test, GasReporter { world.initialize(new CoreModule()); StoreSwitch.setStoreAddress(address(world)); + world.registerNamespace(namespaceId); + world.registerSystem(rootSystemId, rootSystem, true); world.registerSystem(nonRootSystemId, nonRootSystem, true); diff --git a/packages/world/test/WorldDynamicUpdate.t.sol b/packages/world/test/WorldDynamicUpdate.t.sol index f8680e6fa0..a70d6e7b5e 100644 --- a/packages/world/test/WorldDynamicUpdate.t.sol +++ b/packages/world/test/WorldDynamicUpdate.t.sol @@ -64,6 +64,7 @@ contract UpdateInDynamicFieldTest is Test, GasReporter { tableId = WorldResourceIdLib.encode({ typeId: RESOURCE_TABLE, namespace: namespace, name: name }); // Register a new table + world.registerNamespace(tableId.getNamespaceId()); world.registerTable(tableId, fieldLayout, defaultKeySchema, valueSchema, new string[](1), new string[](1)); // Create data