From 590542030e7500f8d3cce6e54e4961d9f8a1a6d5 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Tue, 2 Jan 2024 04:22:59 -0600 Subject: [PATCH 1/3] build: allow use by TypeScript projects with `bundler`/`node16` config (#2084) Co-authored-by: Kevin Ingersoll --- .changeset/poor-beans-beg.md | 23 +++++++++++++++++++++++ packages/abi-ts/tsup.config.ts | 2 +- packages/block-logs-stream/tsup.config.ts | 2 +- packages/common/src/codegen/modules.d.ts | 2 +- packages/common/tsup.config.ts | 2 +- packages/config/tsup.config.ts | 2 +- packages/dev-tools/src/router.tsx | 21 ++++++++++++++++++++- packages/dev-tools/tsup.config.ts | 2 +- packages/faucet/tsup.config.ts | 2 +- packages/gas-report/tsup.config.ts | 2 +- packages/noise/tsup.config.ts | 2 +- packages/phaserx/tsup.config.ts | 2 +- packages/protocol-parser/tsup.config.ts | 2 +- packages/react/tsup.config.ts | 2 +- packages/recs/tsup.config.ts | 2 +- packages/schema-type/tsup.config.ts | 2 +- packages/services/tsup.config.ts | 2 +- packages/store-sync/tsup.config.ts | 2 +- packages/store/tsup.config.ts | 2 +- packages/utils/tsup.config.ts | 2 +- packages/world-modules/tsup.config.ts | 2 +- packages/world/tsup.config.ts | 2 +- 22 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 .changeset/poor-beans-beg.md diff --git a/.changeset/poor-beans-beg.md b/.changeset/poor-beans-beg.md new file mode 100644 index 0000000000..f6e3e38427 --- /dev/null +++ b/.changeset/poor-beans-beg.md @@ -0,0 +1,23 @@ +--- +"@latticexyz/abi-ts": patch +"@latticexyz/block-logs-stream": patch +"@latticexyz/common": patch +"@latticexyz/config": patch +"@latticexyz/dev-tools": patch +"@latticexyz/faucet": patch +"@latticexyz/gas-report": patch +"@latticexyz/noise": patch +"@latticexyz/phaserx": patch +"@latticexyz/protocol-parser": patch +"@latticexyz/react": patch +"@latticexyz/recs": patch +"@latticexyz/schema-type": patch +"@latticexyz/services": patch +"@latticexyz/store-sync": patch +"@latticexyz/store": patch +"@latticexyz/utils": patch +"@latticexyz/world-modules": patch +"@latticexyz/world": patch +--- + +TS packages now generate their respective `.d.ts` type definition files for better compatibility when using MUD with `moduleResolution` set to `bundler` or `node16` and fixes issues around missing type declarations for dependent packages. diff --git a/packages/abi-ts/tsup.config.ts b/packages/abi-ts/tsup.config.ts index b622829fd1..783fa85248 100644 --- a/packages/abi-ts/tsup.config.ts +++ b/packages/abi-ts/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts", "src/abi-ts.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/block-logs-stream/tsup.config.ts b/packages/block-logs-stream/tsup.config.ts index b755469f90..52bdfceee6 100644 --- a/packages/block-logs-stream/tsup.config.ts +++ b/packages/block-logs-stream/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/common/src/codegen/modules.d.ts b/packages/common/src/codegen/modules.d.ts index 12ea8e2ce5..b1d4173a03 100644 --- a/packages/common/src/codegen/modules.d.ts +++ b/packages/common/src/codegen/modules.d.ts @@ -1 +1 @@ -declare module "prettier-plugin-solidity"; +declare module "prettier-plugin-solidity" {} diff --git a/packages/common/tsup.config.ts b/packages/common/tsup.config.ts index 9432329d41..ba8ccf3917 100644 --- a/packages/common/tsup.config.ts +++ b/packages/common/tsup.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ }, target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/config/tsup.config.ts b/packages/config/tsup.config.ts index 6166e27182..ea905447a9 100644 --- a/packages/config/tsup.config.ts +++ b/packages/config/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/library/index.ts", "src/register/index.ts", "src/node/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/dev-tools/src/router.tsx b/packages/dev-tools/src/router.tsx index c2f78f4d64..6b50736e01 100644 --- a/packages/dev-tools/src/router.tsx +++ b/packages/dev-tools/src/router.tsx @@ -9,7 +9,26 @@ import { ComponentData } from "./recs/ComponentData"; import { TablesPage } from "./zustand/TablesPage"; import { TableData } from "./zustand/TableData"; -export const router = createMemoryRouter( +/* +This workaround is necessary to pass `tsc --declaration`. Without it, the following error occurs: + +``` +error TS2742: The inferred type of 'router' cannot be named without a reference to '.pnpm/@remix-run+router@1.6.0/node_modules/@remix-run/router'. +This is likely not portable. A type annotation is necessary. +``` + +This `tsc --declaration` issue arises under the following combined conditions: + +1. pnpm is the package manager. +2. The source uses a function from the dependency package (this time, react-router-dom's createMemoryRouter) and relies on type inference for its return type. +3. The inferred return type originates from a package that is not a direct dependency of the source (this time, @remix-run/router's Router). +4. The dependency package containing the function (react-router-dom) does not re-export the function's return type (Router). + +See https://github.com/microsoft/TypeScript/issues/47663#issuecomment-1519138189 +*/ +type Router = ReturnType; + +export const router: Router = createMemoryRouter( createRoutesFromElements( } errorElement={}> } /> diff --git a/packages/dev-tools/tsup.config.ts b/packages/dev-tools/tsup.config.ts index 5814bb1770..7189afad8e 100644 --- a/packages/dev-tools/tsup.config.ts +++ b/packages/dev-tools/tsup.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/faucet/tsup.config.ts b/packages/faucet/tsup.config.ts index 385f3fc274..cadbc2f970 100644 --- a/packages/faucet/tsup.config.ts +++ b/packages/faucet/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts", "bin/faucet-server.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/gas-report/tsup.config.ts b/packages/gas-report/tsup.config.ts index fafcaa7177..289314bad7 100644 --- a/packages/gas-report/tsup.config.ts +++ b/packages/gas-report/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["ts/index.ts", "ts/gas-report.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/noise/tsup.config.ts b/packages/noise/tsup.config.ts index 264a5bbff8..5837375692 100644 --- a/packages/noise/tsup.config.ts +++ b/packages/noise/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["ts/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/phaserx/tsup.config.ts b/packages/phaserx/tsup.config.ts index b755469f90..52bdfceee6 100644 --- a/packages/phaserx/tsup.config.ts +++ b/packages/phaserx/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/protocol-parser/tsup.config.ts b/packages/protocol-parser/tsup.config.ts index b755469f90..52bdfceee6 100644 --- a/packages/protocol-parser/tsup.config.ts +++ b/packages/protocol-parser/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index b755469f90..52bdfceee6 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/recs/tsup.config.ts b/packages/recs/tsup.config.ts index 71d48b47ec..faf54cff78 100644 --- a/packages/recs/tsup.config.ts +++ b/packages/recs/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts", "src/deprecated/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/schema-type/tsup.config.ts b/packages/schema-type/tsup.config.ts index 4d1fd9f68a..4ae11283a7 100644 --- a/packages/schema-type/tsup.config.ts +++ b/packages/schema-type/tsup.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ outDir: "dist/typescript", target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/services/tsup.config.ts b/packages/services/tsup.config.ts index b57079441a..ed5646e5d5 100644 --- a/packages/services/tsup.config.ts +++ b/packages/services/tsup.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ }, target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, }); diff --git a/packages/store-sync/tsup.config.ts b/packages/store-sync/tsup.config.ts index 184fc6c6ff..a6442c1a4a 100644 --- a/packages/store-sync/tsup.config.ts +++ b/packages/store-sync/tsup.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ ], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/store/tsup.config.ts b/packages/store/tsup.config.ts index e46c0dc59d..031a8e4cb2 100644 --- a/packages/store/tsup.config.ts +++ b/packages/store/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["mud.config.ts", "ts/index.ts", "ts/codegen/index.ts", "ts/config/index.ts", "ts/register/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/utils/tsup.config.ts b/packages/utils/tsup.config.ts index b755469f90..52bdfceee6 100644 --- a/packages/utils/tsup.config.ts +++ b/packages/utils/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["src/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/world-modules/tsup.config.ts b/packages/world-modules/tsup.config.ts index d2aa3e215e..b9ae6d4212 100644 --- a/packages/world-modules/tsup.config.ts +++ b/packages/world-modules/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["mud.config.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, diff --git a/packages/world/tsup.config.ts b/packages/world/tsup.config.ts index 4d76cecf16..d7d1d07098 100644 --- a/packages/world/tsup.config.ts +++ b/packages/world/tsup.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ entry: ["mud.config.ts", "ts/index.ts", "ts/register/index.ts", "ts/node/index.ts"], target: "esnext", format: ["esm"], - dts: false, + dts: true, sourcemap: true, clean: true, minify: true, From de05c33013e467e1501e2637ea077bb607116d37 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Tue, 2 Jan 2024 04:50:48 -0600 Subject: [PATCH 2/3] test(world): add test to ensure all store/world tables are registered (#2064) --- packages/world/foundry.toml | 2 +- packages/world/test/CoreModule.t.sol | 70 +++++++++++++++++++ ...ist-tables-from-store-and-world-configs.ts | 24 +++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 packages/world/test/CoreModule.t.sol create mode 100644 packages/world/ts/scripts/list-tables-from-store-and-world-configs.ts diff --git a/packages/world/foundry.toml b/packages/world/foundry.toml index 3d2a6b756b..637fc99b5f 100644 --- a/packages/world/foundry.toml +++ b/packages/world/foundry.toml @@ -1,6 +1,6 @@ [profile.default] solc = "0.8.21" -ffi = false +ffi = true fuzz_runs = 256 optimizer = true optimizer_runs = 3000 diff --git a/packages/world/test/CoreModule.t.sol b/packages/world/test/CoreModule.t.sol new file mode 100644 index 0000000000..456773fe20 --- /dev/null +++ b/packages/world/test/CoreModule.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +import { Test } from "forge-std/Test.sol"; + +import { StoreRead } from "@latticexyz/store/src/StoreRead.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { Tables } from "@latticexyz/store/src/codegen/index.sol"; + +import { WorldContextProviderLib } from "../src/WorldContext.sol"; +import { CoreModule } from "../src/modules/core/CoreModule.sol"; + +contract WorldMock is StoreRead { + constructor() { + StoreSwitch.setStoreAddress(address(this)); + } + + function delegatecallFromWorld(address target, bytes memory callData) public { + WorldContextProviderLib.delegatecallWithContextOrRevert({ + msgSender: msg.sender, + msgValue: 0, + target: target, + callData: callData + }); + } +} + +contract CoreModuleTest is Test { + CoreModule coreModule; + WorldMock worldMock; + + function setUp() public { + coreModule = new CoreModule(); + worldMock = new WorldMock(); + StoreSwitch.setStoreAddress(address(worldMock)); + } + + function testInstallRoot() public { + // Prepare tableIds for registration validation + bytes32[] memory tableIds = getTableIdsFromStoreAndWorldConfigs(); + + // Invoke installRoot + worldMock.delegatecallFromWorld(address(coreModule), abi.encodeCall(CoreModule.installRoot, (new bytes(0)))); + + // Confirm that each tableId is registered + for (uint256 i; i < tableIds.length; i++) { + bytes32 tableId = tableIds[i]; + assertFalse( + Tables.getFieldLayout(ResourceId.wrap(tableId)).isEmpty(), + string.concat("table should be registered: ", string(abi.encodePacked(tableId))) + ); + } + } + + function getTableIdsFromStoreAndWorldConfigs() private returns (bytes32[] memory) { + string[] memory ffiInputs = new string[](3); + ffiInputs[0] = "pnpm"; + ffiInputs[1] = "tsx"; + ffiInputs[2] = "ts/scripts/list-tables-from-store-and-world-configs.ts"; + + bytes memory ffiOutput = vm.ffi(ffiInputs); + + // The JSONPath `$[*].id` selects the `id` of all elements in the root array + bytes memory encoded = vm.parseJson(string(ffiOutput), "$[*].id"); + bytes32[] memory tableIds = abi.decode(encoded, (bytes32[])); + + return tableIds; + } +} diff --git a/packages/world/ts/scripts/list-tables-from-store-and-world-configs.ts b/packages/world/ts/scripts/list-tables-from-store-and-world-configs.ts new file mode 100644 index 0000000000..544f20e7db --- /dev/null +++ b/packages/world/ts/scripts/list-tables-from-store-and-world-configs.ts @@ -0,0 +1,24 @@ +import { type Hex } from "viem"; + +import { type ExpandMUDUserConfig } from "@latticexyz/store/register"; +import { type MUDCoreUserConfig } from "@latticexyz/config"; + +import { resourceToHex } from "@latticexyz/common"; + +import storeConfig from "@latticexyz/store/mud.config"; +import worldConfig from "../../mud.config"; + +function configToTables(config: ExpandMUDUserConfig): { name: string; id: Hex }[] { + return Object.entries(config.tables) + .filter(([_, table]) => !table.tableIdArgument) // Skip generic tables + .map(([name, table]) => ({ + name: name, + id: resourceToHex({ + type: table.offchainOnly ? "offchainTable" : "table", + namespace: config.namespace, + name: table.name, + }), + })); +} + +console.log(JSON.stringify([...configToTables(storeConfig), ...configToTables(worldConfig)])); From eb384bb0e073b1261b8ab92bc74c32ec4956c886 Mon Sep 17 00:00:00 2001 From: R-Morpheus <107617521+R-Morpheus@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:29:00 +0300 Subject: [PATCH 3/3] feat(world): add isInstalled to Module (#2056) Co-authored-by: Kevin Ingersoll --- .changeset/grumpy-icons-sleep.md | 6 +++++ packages/world-modules/gas-report.json | 22 +++++++++---------- .../src/modules/erc20-puppet/ERC20Module.sol | 7 ++---- .../modules/erc721-puppet/ERC721Module.sol | 8 +++---- .../modules/keysintable/KeysInTableModule.sol | 4 +--- .../keyswithvalue/KeysWithValueModule.sol | 4 +--- .../uniqueentity/UniqueEntityModule.sol | 8 ++----- packages/world/src/Module.sol | 22 +++++++++++++++++++ 8 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 .changeset/grumpy-icons-sleep.md diff --git a/.changeset/grumpy-icons-sleep.md b/.changeset/grumpy-icons-sleep.md new file mode 100644 index 0000000000..c2fdabc713 --- /dev/null +++ b/.changeset/grumpy-icons-sleep.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/world-modules": patch +"@latticexyz/world": patch +--- + +Added `isInstalled` and `requireNotInstalled` helpers to `Module` base contract. diff --git a/packages/world-modules/gas-report.json b/packages/world-modules/gas-report.json index b9e5dddb20..ab8a8fb3dc 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": 1409435 + "gasUsed": 1409529 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1409435 + "gasUsed": 1409529 }, { "file": "test/KeysInTableModule.t.sol", @@ -93,13 +93,13 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1409435 + "gasUsed": 1409529 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1409435 + "gasUsed": 1409529 }, { "file": "test/KeysInTableModule.t.sol", @@ -117,7 +117,7 @@ "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1409435 + "gasUsed": 1409529 }, { "file": "test/KeysInTableModule.t.sol", @@ -135,7 +135,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 650708 + "gasUsed": 650796 }, { "file": "test/KeysWithValueModule.t.sol", @@ -153,7 +153,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 650708 + "gasUsed": 650796 }, { "file": "test/KeysWithValueModule.t.sol", @@ -165,7 +165,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 650708 + "gasUsed": 650796 }, { "file": "test/KeysWithValueModule.t.sol", @@ -183,7 +183,7 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 650708 + "gasUsed": 650796 }, { "file": "test/KeysWithValueModule.t.sol", @@ -303,7 +303,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 678996 + "gasUsed": 679102 }, { "file": "test/UniqueEntityModule.t.sol", @@ -315,7 +315,7 @@ "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 644433 + "gasUsed": 644539 }, { "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 fe1818cd51..5a65cd5af5 100644 --- a/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol +++ b/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol @@ -6,7 +6,6 @@ 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 { InstalledModules } from "@latticexyz/world/src/codegen/tables/InstalledModules.sol"; import { Puppet } from "../puppet/Puppet.sol"; import { createPuppet } from "../puppet/createPuppet.sol"; @@ -32,16 +31,14 @@ contract ERC20Module is Module { function _requireDependencies() internal view { // Require PuppetModule to be installed - if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) { + if (!isInstalled(PUPPET_MODULE_NAME, new bytes(0))) { revert Module_MissingDependency(string(bytes.concat(PUPPET_MODULE_NAME))); } } function install(bytes memory args) public { // Require the module to not be installed with these args yet - if (InstalledModules.get(MODULE_NAME, keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(MODULE_NAME, args); // Extract args (bytes14 namespace, ERC20MetadataData memory metadata) = abi.decode(args, (bytes14, ERC20MetadataData)); diff --git a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol index 446cb4fbfc..6a297e05cd 100644 --- a/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol +++ b/packages/world-modules/src/modules/erc721-puppet/ERC721Module.sol @@ -33,18 +33,16 @@ contract ERC721Module is Module { return MODULE_NAME; } - function _requireDependencies() internal { + function _requireDependencies() internal view { // Require PuppetModule to be installed - if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) { + if (!isInstalled(PUPPET_MODULE_NAME, new bytes(0))) { revert Module_MissingDependency(string(bytes.concat(PUPPET_MODULE_NAME))); } } function install(bytes memory args) public { // Require the module to not be installed with these args yet - if (InstalledModules.get(MODULE_NAME, keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(MODULE_NAME, args); // Extract args (bytes14 namespace, ERC721MetadataData memory metadata) = abi.decode(args, (bytes14, ERC721MetadataData)); diff --git a/packages/world-modules/src/modules/keysintable/KeysInTableModule.sol b/packages/world-modules/src/modules/keysintable/KeysInTableModule.sol index b3172453aa..504a52565a 100644 --- a/packages/world-modules/src/modules/keysintable/KeysInTableModule.sol +++ b/packages/world-modules/src/modules/keysintable/KeysInTableModule.sol @@ -40,9 +40,7 @@ contract KeysInTableModule is Module { function installRoot(bytes memory args) public override { // Naive check to ensure this is only installed once // TODO: only revert if there's nothing to do - if (InstalledModules.getModuleAddress(getName(), keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(getName(), args); // Extract source table id from args ResourceId sourceTableId = ResourceId.wrap(abi.decode(args, (bytes32))); diff --git a/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol b/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol index 1810ee7fa8..7ca3362bba 100644 --- a/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol +++ b/packages/world-modules/src/modules/keyswithvalue/KeysWithValueModule.sol @@ -41,9 +41,7 @@ contract KeysWithValueModule is Module { function installRoot(bytes memory args) public { // Naive check to ensure this is only installed once // TODO: only revert if there's nothing to do - if (InstalledModules.getModuleAddress(getName(), keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(getName(), args); // Extract source table id from args ResourceId sourceTableId = ResourceId.wrap(abi.decode(args, (bytes32))); diff --git a/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol b/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol index af56d05be7..36ddb01e8a 100644 --- a/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol +++ b/packages/world-modules/src/modules/uniqueentity/UniqueEntityModule.sol @@ -29,9 +29,7 @@ contract UniqueEntityModule is Module { function installRoot(bytes memory args) public { // Naive check to ensure this is only installed once // TODO: only revert if there's nothing to do - if (InstalledModules.getModuleAddress(getName(), keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(getName(), args); IBaseWorld world = IBaseWorld(_world()); @@ -54,9 +52,7 @@ contract UniqueEntityModule is Module { function install(bytes memory args) public { // Naive check to ensure this is only installed once // TODO: only revert if there's nothing to do - if (InstalledModules.getModuleAddress(getName(), keccak256(args)) != address(0)) { - revert Module_AlreadyInstalled(); - } + requireNotInstalled(getName(), args); IBaseWorld world = IBaseWorld(_world()); diff --git a/packages/world/src/Module.sol b/packages/world/src/Module.sol index 6bf4c64921..a39219e422 100644 --- a/packages/world/src/Module.sol +++ b/packages/world/src/Module.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.21; import { WorldContextConsumer } from "./WorldContext.sol"; import { IModule, MODULE_INTERFACE_ID } from "./IModule.sol"; import { IERC165, ERC165_INTERFACE_ID } from "./IERC165.sol"; +import { InstalledModules } from "./codegen/tables/InstalledModules.sol"; /** * @title Module @@ -21,4 +22,25 @@ abstract contract Module is IModule, WorldContextConsumer { ) public pure virtual override(IERC165, WorldContextConsumer) returns (bool) { return interfaceId == MODULE_INTERFACE_ID || interfaceId == ERC165_INTERFACE_ID; } + + /** + * @dev Check if a module with the given name and arguments is installed. + * @param moduleName The name of the module. + * @param args The arguments for the module installation. + * @return true if the module is installed, false otherwise. + */ + function isInstalled(bytes16 moduleName, bytes memory args) internal view returns (bool) { + return InstalledModules.get(moduleName, keccak256(args)) != address(0); + } + + /** + * @dev Revert if the module with the given name and arguments is already installed. + * @param moduleName The name of the module. + * @param args The arguments for the module installation. + */ + function requireNotInstalled(bytes16 moduleName, bytes memory args) internal view { + if (isInstalled(moduleName, args)) { + revert Module_AlreadyInstalled(); + } + } }