From c049c23f48b93ac7881fb1a5a8417831611d5cbf Mon Sep 17 00:00:00 2001
From: alvarius <alvarius@lattice.xyz>
Date: Wed, 13 Sep 2023 23:05:20 +0100
Subject: [PATCH] feat(world,store): add initialize method, initialize core
 tables in core module (#1472)

---
 .changeset/strong-months-push.md              |   9 +
 .changeset/tricky-beds-kiss.md                |  24 +
 packages/cli/contracts/test/Tablegen.t.sol    |   4 +-
 packages/cli/src/utils/deploy.ts              |   2 +-
 .../abi/StoreMock.sol/StoreMock.abi.json      |   5 +
 .../abi/StoreMock.sol/StoreMock.abi.json.d.ts |   5 +
 .../abi/StoreRead.sol/StoreRead.abi.json      | 122 ---
 .../abi/StoreRead.sol/StoreRead.abi.json.d.ts | 122 ---
 .../StoreReadWithStubs.abi.json               | 780 -----------------
 .../StoreReadWithStubs.abi.json.d.ts          | 781 ------------------
 packages/store/gas-report.json                |   6 +-
 packages/store/src/StoreCore.sol              |  11 +-
 packages/store/src/StoreRead.sol              |   4 -
 packages/store/src/StoreReadWithStubs.sol     |  87 --
 packages/store/test/KeyEncoding.t.sol         |   4 +-
 packages/store/test/Mixed.t.sol               |   4 +-
 packages/store/test/StoreCore.t.sol           |   2 +-
 packages/store/test/StoreCoreDynamic.t.sol    |   4 +-
 packages/store/test/StoreCoreGas.t.sol        |   2 +-
 packages/store/test/StoreMock.sol             |  30 +-
 packages/store/test/StoreSwitch.t.sol         |   4 +-
 packages/store/test/Vector2.t.sol             |   4 +-
 packages/store/test/tables/Callbacks.t.sol    |   4 +-
 packages/store/test/tables/StoreHooks.t.sol   |   4 +-
 .../test/tables/StoreHooksColdLoad.t.sol      |   4 +-
 .../BalanceTransferSystem.abi.json            |  16 +-
 .../BalanceTransferSystem.abi.json.d.ts       |  16 +-
 .../abi/CoreSystem.sol/CoreSystem.abi.json    |  16 +-
 .../CoreSystem.sol/CoreSystem.abi.json.d.ts   |  16 +-
 .../abi/IBaseWorld.sol/IBaseWorld.abi.json    |  42 +-
 .../IBaseWorld.sol/IBaseWorld.abi.json.d.ts   |  42 +-
 .../IWorldErrors.sol/IWorldErrors.abi.json    |  16 +-
 .../IWorldErrors.abi.json.d.ts                |  16 +-
 .../IWorldKernel.sol/IWorldKernel.abi.json    |  42 +-
 .../IWorldKernel.abi.json.d.ts                |  42 +-
 .../abi/StoreRead.sol/StoreRead.abi.json      | 122 ---
 .../abi/StoreRead.sol/StoreRead.abi.json.d.ts | 122 ---
 .../StoreRegistrationSystem.abi.json          |  16 +-
 .../StoreRegistrationSystem.abi.json.d.ts     |  16 +-
 packages/world/abi/World.sol/World.abi.json   | 122 +--
 .../world/abi/World.sol/World.abi.json.d.ts   | 122 +--
 .../WorldRegistrationSystem.abi.json          |  16 +-
 .../WorldRegistrationSystem.abi.json.d.ts     |  16 +-
 packages/world/gas-report.json                | 112 +--
 packages/world/src/World.sol                  |  32 +-
 packages/world/src/factories/WorldFactory.sol |   2 +-
 .../world/src/interfaces/IWorldErrors.sol     |   2 +-
 .../world/src/interfaces/IWorldKernel.sol     |  10 +
 .../world/src/modules/core/CoreModule.sol     |   4 +
 packages/world/test/AccessControl.t.sol       |   4 +-
 packages/world/test/KeysInTableModule.t.sol   |   2 +-
 packages/world/test/KeysWithValueModule.t.sol |   2 +-
 .../test/StandardDelegationsModule.t.sol      |   2 +-
 packages/world/test/UniqueEntityModule.t.sol  |   2 +-
 packages/world/test/Utils.t.sol               |   2 +-
 packages/world/test/World.t.sol               |  47 +-
 packages/world/test/WorldBalance.t.sol        |   2 +-
 packages/world/test/WorldDynamicUpdate.t.sol  |   2 +-
 packages/world/test/query.t.sol               |   2 +-
 59 files changed, 478 insertions(+), 2595 deletions(-)
 create mode 100644 .changeset/strong-months-push.md
 create mode 100644 .changeset/tricky-beds-kiss.md
 delete mode 100644 packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json
 delete mode 100644 packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json.d.ts
 delete mode 100644 packages/store/src/StoreReadWithStubs.sol

diff --git a/.changeset/strong-months-push.md b/.changeset/strong-months-push.md
new file mode 100644
index 0000000000..42b321ce1c
--- /dev/null
+++ b/.changeset/strong-months-push.md
@@ -0,0 +1,9 @@
+---
+"@latticexyz/store": major
+---
+
+- `StoreCore`'s `initialize` function is split into `initialize` (to set the `StoreSwitch`'s `storeAddress`) and `registerCoreTables` (to register the `Tables` and `StoreHooks` tables).
+  The purpose of this is to give consumers more granular control over the setup flow.
+
+- The `StoreRead` contract no longer calls `StoreCore.initialize` in its constructor.
+  `StoreCore` consumers are expected to call `StoreCore.initialize` and `StoreCore.registerCoreTable` in their own setup logic.
diff --git a/.changeset/tricky-beds-kiss.md b/.changeset/tricky-beds-kiss.md
new file mode 100644
index 0000000000..151ec9202a
--- /dev/null
+++ b/.changeset/tricky-beds-kiss.md
@@ -0,0 +1,24 @@
+---
+"@latticexyz/cli": patch
+"@latticexyz/world": minor
+---
+
+- The `World` contract now has an `initialize` function, which can be called once by the creator of the World to install the core module.
+  This change allows the registration of all core tables to happen in the `CoreModule`, so no table metadata has to be included in the `World`'s bytecode.
+
+  ```solidity
+  interface IBaseWorld {
+    function initialize(IModule coreModule) public;
+  }
+  ```
+
+- The `World` contract now stores the original creator of the `World` in an immutable state variable.
+  It is used internally to only allow the original creator to initialize the `World` in a separate transaction.
+
+  ```solidity
+  interface IBaseWorld {
+    function creator() external view returns (address);
+  }
+  ```
+
+- The deploy script is updated to use the `World`'s `initialize` function to install the `CoreModule` instead of `registerRootModule` as before.
diff --git a/packages/cli/contracts/test/Tablegen.t.sol b/packages/cli/contracts/test/Tablegen.t.sol
index cf840e6387..29f6ec8b29 100644
--- a/packages/cli/contracts/test/Tablegen.t.sol
+++ b/packages/cli/contracts/test/Tablegen.t.sol
@@ -2,13 +2,13 @@
 pragma solidity >=0.8.0;
 
 import "forge-std/Test.sol";
-import { StoreReadWithStubs } from "@latticexyz/store/src/StoreReadWithStubs.sol";
+import { StoreMock } from "@latticexyz/store/test/StoreMock.sol";
 
 import { Statics, StaticsData, Dynamics1, Dynamics1Data, Dynamics2, Dynamics2Data, Singleton, Ephemeral } from "../src/codegen/Tables.sol";
 
 import { Enum1, Enum2 } from "../src/codegen/Types.sol";
 
-contract TablegenTest is Test, StoreReadWithStubs {
+contract TablegenTest is Test, StoreMock {
   function testStaticsSetAndGet() public {
     Statics.register();
 
diff --git a/packages/cli/src/utils/deploy.ts b/packages/cli/src/utils/deploy.ts
index 76b2df2993..a9d1cb31fc 100644
--- a/packages/cli/src/utils/deploy.ts
+++ b/packages/cli/src/utils/deploy.ts
@@ -136,7 +136,7 @@ export async function deploy(
   // Install core Modules
   if (!worldAddress) {
     console.log(chalk.blue("Installing core World modules"));
-    await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.CoreModule, "0x"], confirmations);
+    await fastTxExecute(WorldContract, "initialize", [await modulePromises.CoreModule], confirmations);
     console.log(chalk.green("Installed core World modules"));
   }
 
diff --git a/packages/store/abi/StoreMock.sol/StoreMock.abi.json b/packages/store/abi/StoreMock.sol/StoreMock.abi.json
index 2bdd2738ee..06aaab4969 100644
--- a/packages/store/abi/StoreMock.sol/StoreMock.abi.json
+++ b/packages/store/abi/StoreMock.sol/StoreMock.abi.json
@@ -1,4 +1,9 @@
 [
+  {
+    "inputs": [],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
   {
     "inputs": [
       {
diff --git a/packages/store/abi/StoreMock.sol/StoreMock.abi.json.d.ts b/packages/store/abi/StoreMock.sol/StoreMock.abi.json.d.ts
index e9dfdc5fd2..7c1b063fd5 100644
--- a/packages/store/abi/StoreMock.sol/StoreMock.abi.json.d.ts
+++ b/packages/store/abi/StoreMock.sol/StoreMock.abi.json.d.ts
@@ -1,4 +1,9 @@
 declare const abi: [
+  {
+    inputs: [];
+    stateMutability: "nonpayable";
+    type: "constructor";
+  },
   {
     inputs: [
       {
diff --git a/packages/store/abi/StoreRead.sol/StoreRead.abi.json b/packages/store/abi/StoreRead.sol/StoreRead.abi.json
index 1f8dd917db..d667734d9d 100644
--- a/packages/store/abi/StoreRead.sol/StoreRead.abi.json
+++ b/packages/store/abi/StoreRead.sol/StoreRead.abi.json
@@ -1,9 +1,4 @@
 [
-  {
-    "inputs": [],
-    "stateMutability": "nonpayable",
-    "type": "constructor"
-  },
   {
     "inputs": [
       {
@@ -25,128 +20,11 @@
     "name": "FieldLayoutLib_StaticLengthIsZero",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      }
-    ],
-    "name": "SchemaLib_InvalidLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "SchemaLib_StaticTypeAfterDynamicType",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      },
-      {
-        "internalType": "uint256",
-        "name": "start",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "end",
-        "type": "uint256"
-      }
-    ],
-    "name": "Slice_OutOfBounds",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidDataLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidFieldNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidKeyNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidValueSchemaLength",
-    "type": "error"
-  },
   {
     "inputs": [],
     "name": "StoreCore_NotDynamicField",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string",
-        "name": "tableIdString",
-        "type": "string"
-      }
-    ],
-    "name": "StoreCore_TableAlreadyExists",
-    "type": "error"
-  },
   {
     "inputs": [
       {
diff --git a/packages/store/abi/StoreRead.sol/StoreRead.abi.json.d.ts b/packages/store/abi/StoreRead.sol/StoreRead.abi.json.d.ts
index 677134cc99..b55d3b54f0 100644
--- a/packages/store/abi/StoreRead.sol/StoreRead.abi.json.d.ts
+++ b/packages/store/abi/StoreRead.sol/StoreRead.abi.json.d.ts
@@ -1,9 +1,4 @@
 declare const abi: [
-  {
-    inputs: [];
-    stateMutability: "nonpayable";
-    type: "constructor";
-  },
   {
     inputs: [
       {
@@ -25,128 +20,11 @@ declare const abi: [
     name: "FieldLayoutLib_StaticLengthIsZero";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      }
-    ];
-    name: "SchemaLib_InvalidLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "SchemaLib_StaticTypeAfterDynamicType";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      },
-      {
-        internalType: "uint256";
-        name: "start";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "end";
-        type: "uint256";
-      }
-    ];
-    name: "Slice_OutOfBounds";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidDataLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidFieldNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidKeyNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidValueSchemaLength";
-    type: "error";
-  },
   {
     inputs: [];
     name: "StoreCore_NotDynamicField";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "string";
-        name: "tableIdString";
-        type: "string";
-      }
-    ];
-    name: "StoreCore_TableAlreadyExists";
-    type: "error";
-  },
   {
     inputs: [
       {
diff --git a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json b/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json
deleted file mode 100644
index ff1fab95f9..0000000000
--- a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json
+++ /dev/null
@@ -1,780 +0,0 @@
-[
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      }
-    ],
-    "name": "FieldLayoutLib_InvalidLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "FieldLayoutLib_StaticLengthDoesNotFitInAWord",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "FieldLayoutLib_StaticLengthIsZero",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      }
-    ],
-    "name": "SchemaLib_InvalidLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "SchemaLib_StaticTypeAfterDynamicType",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      },
-      {
-        "internalType": "uint256",
-        "name": "start",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "end",
-        "type": "uint256"
-      }
-    ],
-    "name": "Slice_OutOfBounds",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_DataIndexOverflow",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidDataLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidFieldNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidKeyNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidValueSchemaLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "StoreCore_NotDynamicField",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "StoreCore_NotImplemented",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string",
-        "name": "tableIdString",
-        "type": "string"
-      }
-    ],
-    "name": "StoreCore_TableAlreadyExists",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string",
-        "name": "tableIdString",
-        "type": "string"
-      }
-    ],
-    "name": "StoreCore_TableNotFound",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "StoreReadWithStubs_NotImplemented",
-    "type": "error"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      }
-    ],
-    "name": "StoreDeleteRecord",
-    "type": "event"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      }
-    ],
-    "name": "StoreEphemeralRecord",
-    "type": "event"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "indexed": false,
-        "internalType": "uint8",
-        "name": "schemaIndex",
-        "type": "uint8"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      }
-    ],
-    "name": "StoreSetField",
-    "type": "event"
-  },
-  {
-    "anonymous": false,
-    "inputs": [
-      {
-        "indexed": false,
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "indexed": false,
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      }
-    ],
-    "name": "StoreSetRecord",
-    "type": "event"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "deleteRecord",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "emitEphemeralRecord",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "schemaIndex",
-        "type": "uint8"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "fieldLayout",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getField",
-    "outputs": [
-      {
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getFieldLayout",
-    "outputs": [
-      {
-        "internalType": "FieldLayout",
-        "name": "fieldLayout",
-        "type": "bytes32"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "schemaIndex",
-        "type": "uint8"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "fieldLayout",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getFieldLength",
-    "outputs": [
-      {
-        "internalType": "uint256",
-        "name": "",
-        "type": "uint256"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "schemaIndex",
-        "type": "uint8"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "fieldLayout",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "uint256",
-        "name": "start",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "end",
-        "type": "uint256"
-      }
-    ],
-    "name": "getFieldSlice",
-    "outputs": [
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getKeySchema",
-    "outputs": [
-      {
-        "internalType": "Schema",
-        "name": "schema",
-        "type": "bytes32"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "key",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "fieldLayout",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getRecord",
-    "outputs": [
-      {
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "table",
-        "type": "bytes32"
-      }
-    ],
-    "name": "getValueSchema",
-    "outputs": [
-      {
-        "internalType": "Schema",
-        "name": "schema",
-        "type": "bytes32"
-      }
-    ],
-    "stateMutability": "view",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "",
-        "type": "uint8"
-      },
-      {
-        "internalType": "uint256",
-        "name": "",
-        "type": "uint256"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "popFromField",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "",
-        "type": "uint8"
-      },
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "pushToField",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "contract IStoreHook",
-        "name": "",
-        "type": "address"
-      },
-      {
-        "internalType": "uint8",
-        "name": "",
-        "type": "uint8"
-      }
-    ],
-    "name": "registerStoreHook",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "Schema",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "Schema",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string[]",
-        "name": "",
-        "type": "string[]"
-      },
-      {
-        "internalType": "string[]",
-        "name": "",
-        "type": "string[]"
-      }
-    ],
-    "name": "registerTable",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "",
-        "type": "uint8"
-      },
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "setField",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "setRecord",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "contract IStoreHook",
-        "name": "",
-        "type": "address"
-      }
-    ],
-    "name": "unregisterStoreHook",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "bytes32[]",
-        "name": "",
-        "type": "bytes32[]"
-      },
-      {
-        "internalType": "uint8",
-        "name": "",
-        "type": "uint8"
-      },
-      {
-        "internalType": "uint256",
-        "name": "",
-        "type": "uint256"
-      },
-      {
-        "internalType": "bytes",
-        "name": "",
-        "type": "bytes"
-      },
-      {
-        "internalType": "FieldLayout",
-        "name": "",
-        "type": "bytes32"
-      }
-    ],
-    "name": "updateInField",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  }
-]
\ No newline at end of file
diff --git a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json.d.ts b/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json.d.ts
deleted file mode 100644
index 667d11271a..0000000000
--- a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json.d.ts
+++ /dev/null
@@ -1,781 +0,0 @@
-declare const abi: [
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      }
-    ];
-    name: "FieldLayoutLib_InvalidLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "FieldLayoutLib_StaticLengthDoesNotFitInAWord";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "FieldLayoutLib_StaticLengthIsZero";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      }
-    ];
-    name: "SchemaLib_InvalidLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "SchemaLib_StaticTypeAfterDynamicType";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      },
-      {
-        internalType: "uint256";
-        name: "start";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "end";
-        type: "uint256";
-      }
-    ];
-    name: "Slice_OutOfBounds";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_DataIndexOverflow";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidDataLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidFieldNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidKeyNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidValueSchemaLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "StoreCore_NotDynamicField";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "StoreCore_NotImplemented";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "string";
-        name: "tableIdString";
-        type: "string";
-      }
-    ];
-    name: "StoreCore_TableAlreadyExists";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "string";
-        name: "tableIdString";
-        type: "string";
-      }
-    ];
-    name: "StoreCore_TableNotFound";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "StoreReadWithStubs_NotImplemented";
-    type: "error";
-  },
-  {
-    anonymous: false;
-    inputs: [
-      {
-        indexed: false;
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        indexed: false;
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      }
-    ];
-    name: "StoreDeleteRecord";
-    type: "event";
-  },
-  {
-    anonymous: false;
-    inputs: [
-      {
-        indexed: false;
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        indexed: false;
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        indexed: false;
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      }
-    ];
-    name: "StoreEphemeralRecord";
-    type: "event";
-  },
-  {
-    anonymous: false;
-    inputs: [
-      {
-        indexed: false;
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        indexed: false;
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        indexed: false;
-        internalType: "uint8";
-        name: "schemaIndex";
-        type: "uint8";
-      },
-      {
-        indexed: false;
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      }
-    ];
-    name: "StoreSetField";
-    type: "event";
-  },
-  {
-    anonymous: false;
-    inputs: [
-      {
-        indexed: false;
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        indexed: false;
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        indexed: false;
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      }
-    ];
-    name: "StoreSetRecord";
-    type: "event";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "deleteRecord";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "emitEphemeralRecord";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "schemaIndex";
-        type: "uint8";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "fieldLayout";
-        type: "bytes32";
-      }
-    ];
-    name: "getField";
-    outputs: [
-      {
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      }
-    ];
-    name: "getFieldLayout";
-    outputs: [
-      {
-        internalType: "FieldLayout";
-        name: "fieldLayout";
-        type: "bytes32";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "schemaIndex";
-        type: "uint8";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "fieldLayout";
-        type: "bytes32";
-      }
-    ];
-    name: "getFieldLength";
-    outputs: [
-      {
-        internalType: "uint256";
-        name: "";
-        type: "uint256";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "schemaIndex";
-        type: "uint8";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "fieldLayout";
-        type: "bytes32";
-      },
-      {
-        internalType: "uint256";
-        name: "start";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "end";
-        type: "uint256";
-      }
-    ];
-    name: "getFieldSlice";
-    outputs: [
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      }
-    ];
-    name: "getKeySchema";
-    outputs: [
-      {
-        internalType: "Schema";
-        name: "schema";
-        type: "bytes32";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "key";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "fieldLayout";
-        type: "bytes32";
-      }
-    ];
-    name: "getRecord";
-    outputs: [
-      {
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "table";
-        type: "bytes32";
-      }
-    ];
-    name: "getValueSchema";
-    outputs: [
-      {
-        internalType: "Schema";
-        name: "schema";
-        type: "bytes32";
-      }
-    ];
-    stateMutability: "view";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "";
-        type: "uint8";
-      },
-      {
-        internalType: "uint256";
-        name: "";
-        type: "uint256";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "popFromField";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "";
-        type: "uint8";
-      },
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "pushToField";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "contract IStoreHook";
-        name: "";
-        type: "address";
-      },
-      {
-        internalType: "uint8";
-        name: "";
-        type: "uint8";
-      }
-    ];
-    name: "registerStoreHook";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "Schema";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "Schema";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "string[]";
-        name: "";
-        type: "string[]";
-      },
-      {
-        internalType: "string[]";
-        name: "";
-        type: "string[]";
-      }
-    ];
-    name: "registerTable";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "";
-        type: "uint8";
-      },
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "setField";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "setRecord";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "contract IStoreHook";
-        name: "";
-        type: "address";
-      }
-    ];
-    name: "unregisterStoreHook";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "";
-        type: "bytes32";
-      },
-      {
-        internalType: "bytes32[]";
-        name: "";
-        type: "bytes32[]";
-      },
-      {
-        internalType: "uint8";
-        name: "";
-        type: "uint8";
-      },
-      {
-        internalType: "uint256";
-        name: "";
-        type: "uint256";
-      },
-      {
-        internalType: "bytes";
-        name: "";
-        type: "bytes";
-      },
-      {
-        internalType: "FieldLayout";
-        name: "";
-        type: "bytes32";
-      }
-    ];
-    name: "updateInField";
-    outputs: [];
-    stateMutability: "nonpayable";
-    type: "function";
-  }
-];
-export default abi;
diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json
index cf9bba9495..0899ffa898 100644
--- a/packages/store/gas-report.json
+++ b/packages/store/gas-report.json
@@ -309,7 +309,7 @@
     "file": "test/KeyEncoding.t.sol",
     "test": "testRegisterAndGetFieldLayout",
     "name": "register KeyEncoding table",
-    "gasUsed": 697846
+    "gasUsed": 697905
   },
   {
     "file": "test/Mixed.t.sol",
@@ -903,7 +903,7 @@
     "file": "test/tables/Callbacks.t.sol",
     "test": "testSetAndGet",
     "name": "Callbacks: push 1 element",
-    "gasUsed": 39000
+    "gasUsed": 39001
   },
   {
     "file": "test/tables/StoreHooks.t.sol",
@@ -1059,7 +1059,7 @@
     "file": "test/Vector2.t.sol",
     "test": "testRegisterAndGetFieldLayout",
     "name": "register Vector2 field layout",
-    "gasUsed": 421468
+    "gasUsed": 421491
   },
   {
     "file": "test/Vector2.t.sol",
diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol
index 7c52c5280d..a9f2540143 100644
--- a/packages/store/src/StoreCore.sol
+++ b/packages/store/src/StoreCore.sol
@@ -23,7 +23,7 @@ library StoreCore {
   event StoreEphemeralRecord(bytes32 table, bytes32[] key, bytes data);
 
   /**
-   * Initialize internal tables.
+   * Intialize the store address to use in StoreSwitch.
    * Consumers must call this function in their constructor.
    */
   function initialize() internal {
@@ -31,8 +31,15 @@ library StoreCore {
     // If StoreSwitch is called in the context of a Store contract (storeAddress == address(this)),
     // StoreSwitch uses internal methods to write data instead of external calls.
     StoreSwitch.setStoreAddress(address(this));
+  }
 
-    // Register internal tables
+  /**
+   * Register core tables.
+   * Consumers must call this function in their constructor before setting
+   * any table data to allow indexers to decode table events.
+   */
+  function registerCoreTables() internal {
+    // Register core tables
     Tables.register();
     StoreHooks.register();
   }
diff --git a/packages/store/src/StoreRead.sol b/packages/store/src/StoreRead.sol
index 83ca38b864..28fc1ff91a 100644
--- a/packages/store/src/StoreRead.sol
+++ b/packages/store/src/StoreRead.sol
@@ -7,10 +7,6 @@ import { FieldLayout } from "./FieldLayout.sol";
 import { Schema } from "./Schema.sol";
 
 contract StoreRead is IStoreRead {
-  constructor() {
-    StoreCore.initialize();
-  }
-
   function getFieldLayout(bytes32 table) public view virtual returns (FieldLayout fieldLayout) {
     fieldLayout = StoreCore.getFieldLayout(table);
   }
diff --git a/packages/store/src/StoreReadWithStubs.sol b/packages/store/src/StoreReadWithStubs.sol
deleted file mode 100644
index 71c55ccfca..0000000000
--- a/packages/store/src/StoreReadWithStubs.sol
+++ /dev/null
@@ -1,87 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity >=0.8.0;
-
-import { IStore, IStoreHook } from "./IStore.sol";
-import { StoreCore } from "./StoreCore.sol";
-import { FieldLayout } from "./FieldLayout.sol";
-import { Schema } from "./Schema.sol";
-import { StoreRead } from "./StoreRead.sol";
-
-/**
- * StoreReadWithStubs is not abstract and has signatures for all IStore methods,
- * but only implements read methods (inheriting from StoreRead),
- * so it can be used as IStore mock for testing or when write methods are not needed.
- */
-contract StoreReadWithStubs is IStore, StoreRead {
-  error StoreReadWithStubs_NotImplemented();
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function registerTable(bytes32, FieldLayout, Schema, Schema, string[] calldata, string[] calldata) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function setRecord(bytes32, bytes32[] calldata, bytes calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function setField(bytes32, bytes32[] calldata, uint8, bytes calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function pushToField(bytes32, bytes32[] calldata, uint8, bytes calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function popFromField(bytes32, bytes32[] calldata, uint8, uint256, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function updateInField(bytes32, bytes32[] calldata, uint8, uint256, bytes calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function registerStoreHook(bytes32, IStoreHook, uint8) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function unregisterStoreHook(bytes32, IStoreHook) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function deleteRecord(bytes32, bytes32[] calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-
-  /**
-   * Not implemented in StoreReadWithStubs
-   */
-  function emitEphemeralRecord(bytes32, bytes32[] calldata, bytes calldata, FieldLayout) public virtual {
-    revert StoreReadWithStubs_NotImplemented();
-  }
-}
diff --git a/packages/store/test/KeyEncoding.t.sol b/packages/store/test/KeyEncoding.t.sol
index a697813d28..1c959ba75b 100644
--- a/packages/store/test/KeyEncoding.t.sol
+++ b/packages/store/test/KeyEncoding.t.sol
@@ -6,11 +6,11 @@ import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
 import { KeyEncoding, KeyEncodingTableId } from "../src/codegen/Tables.sol";
 import { ExampleEnum } from "../src/codegen/Types.sol";
 import { StoreCore } from "../src/StoreCore.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
 
-contract KeyEncodingTest is Test, GasReporter, StoreReadWithStubs {
+contract KeyEncodingTest is Test, GasReporter, StoreMock {
   function testRegisterAndGetFieldLayout() public {
     startGasReport("register KeyEncoding table");
     KeyEncoding.register();
diff --git a/packages/store/test/Mixed.t.sol b/packages/store/test/Mixed.t.sol
index 9719998447..b38f714d53 100644
--- a/packages/store/test/Mixed.t.sol
+++ b/packages/store/test/Mixed.t.sol
@@ -5,11 +5,11 @@ import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
 import { Mixed, MixedData, MixedTableId } from "../src/codegen/Tables.sol";
 import { StoreCore } from "../src/StoreCore.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
 
-contract MixedTest is Test, GasReporter, StoreReadWithStubs {
+contract MixedTest is Test, GasReporter, StoreMock {
   MixedData private testMixed;
 
   function testRegisterAndGetFieldLayout() public {
diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol
index c0b9f703be..15892cb85c 100644
--- a/packages/store/test/StoreCore.t.sol
+++ b/packages/store/test/StoreCore.t.sol
@@ -10,7 +10,7 @@ import { EncodeArray } from "../src/tightcoder/EncodeArray.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
 import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { IStoreErrors } from "../src/IStoreErrors.sol";
 import { IStore } from "../src/IStore.sol";
 import { StoreSwitch } from "../src/StoreSwitch.sol";
diff --git a/packages/store/test/StoreCoreDynamic.t.sol b/packages/store/test/StoreCoreDynamic.t.sol
index 00de8cd813..19a08dd88c 100644
--- a/packages/store/test/StoreCoreDynamic.t.sol
+++ b/packages/store/test/StoreCoreDynamic.t.sol
@@ -9,11 +9,11 @@ import { SliceLib } from "../src/Slice.sol";
 import { EncodeArray } from "../src/tightcoder/EncodeArray.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol";
 import { SchemaEncodeHelper } from "./SchemaEncodeHelper.sol";
 
-contract StoreCoreDynamicTest is Test, GasReporter, StoreReadWithStubs {
+contract StoreCoreDynamicTest is Test, GasReporter, StoreMock {
   Schema internal defaultKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32);
 
   bytes32[] internal _key;
diff --git a/packages/store/test/StoreCoreGas.t.sol b/packages/store/test/StoreCoreGas.t.sol
index ba47a49fe8..97e2ec65a6 100644
--- a/packages/store/test/StoreCoreGas.t.sol
+++ b/packages/store/test/StoreCoreGas.t.sol
@@ -11,7 +11,7 @@ import { EncodeArray } from "../src/tightcoder/EncodeArray.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
 import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { IStoreErrors } from "../src/IStoreErrors.sol";
 import { IStore } from "../src/IStore.sol";
 import { FieldLayoutEncodeHelper } from "./FieldLayoutEncodeHelper.sol";
diff --git a/packages/store/test/StoreMock.sol b/packages/store/test/StoreMock.sol
index c0535f6368..8338942a8a 100644
--- a/packages/store/test/StoreMock.sol
+++ b/packages/store/test/StoreMock.sol
@@ -11,8 +11,18 @@ import { StoreRead } from "../src/StoreRead.sol";
  * StoreMock is a contract wrapper around the StoreCore library for testing purposes.
  */
 contract StoreMock is IStore, StoreRead {
+  constructor() {
+    StoreCore.initialize();
+    StoreCore.registerCoreTables();
+  }
+
   // Set full record (including full dynamic data)
-  function setRecord(bytes32 table, bytes32[] calldata key, bytes calldata data, FieldLayout fieldLayout) public {
+  function setRecord(
+    bytes32 table,
+    bytes32[] calldata key,
+    bytes calldata data,
+    FieldLayout fieldLayout
+  ) public virtual {
     StoreCore.setRecord(table, key, data, fieldLayout);
   }
 
@@ -23,7 +33,7 @@ contract StoreMock is IStore, StoreRead {
     uint8 schemaIndex,
     bytes calldata data,
     FieldLayout fieldLayout
-  ) public {
+  ) public virtual {
     StoreCore.setField(table, key, schemaIndex, data, fieldLayout);
   }
 
@@ -34,7 +44,7 @@ contract StoreMock is IStore, StoreRead {
     uint8 schemaIndex,
     bytes calldata dataToPush,
     FieldLayout fieldLayout
-  ) public {
+  ) public virtual {
     StoreCore.pushToField(table, key, schemaIndex, dataToPush, fieldLayout);
   }
 
@@ -45,7 +55,7 @@ contract StoreMock is IStore, StoreRead {
     uint8 schemaIndex,
     uint256 byteLengthToPop,
     FieldLayout fieldLayout
-  ) public {
+  ) public virtual {
     StoreCore.popFromField(table, key, schemaIndex, byteLengthToPop, fieldLayout);
   }
 
@@ -57,12 +67,12 @@ contract StoreMock is IStore, StoreRead {
     uint256 startByteIndex,
     bytes calldata dataToSet,
     FieldLayout fieldLayout
-  ) public {
+  ) public virtual {
     StoreCore.updateInField(table, key, schemaIndex, startByteIndex, dataToSet, fieldLayout);
   }
 
   // Set full record (including full dynamic data)
-  function deleteRecord(bytes32 table, bytes32[] memory key, FieldLayout fieldLayout) public {
+  function deleteRecord(bytes32 table, bytes32[] memory key, FieldLayout fieldLayout) public virtual {
     StoreCore.deleteRecord(table, key, fieldLayout);
   }
 
@@ -72,7 +82,7 @@ contract StoreMock is IStore, StoreRead {
     bytes32[] calldata key,
     bytes calldata data,
     FieldLayout fieldLayout
-  ) public {
+  ) public virtual {
     StoreCore.emitEphemeralRecord(table, key, data, fieldLayout);
   }
 
@@ -83,17 +93,17 @@ contract StoreMock is IStore, StoreRead {
     Schema valueSchema,
     string[] calldata keyNames,
     string[] calldata fieldNames
-  ) public {
+  ) public virtual {
     StoreCore.registerTable(table, fieldLayout, keySchema, valueSchema, keyNames, fieldNames);
   }
 
   // Register hook to be called when a record or field is set or deleted
-  function registerStoreHook(bytes32 table, IStoreHook hookAddress, uint8 enabledHooksBitmap) public {
+  function registerStoreHook(bytes32 table, IStoreHook hookAddress, uint8 enabledHooksBitmap) public virtual {
     StoreCore.registerStoreHook(table, hookAddress, enabledHooksBitmap);
   }
 
   // Unregister hook to be called when a record or field is set or deleted
-  function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) public {
+  function unregisterStoreHook(bytes32 table, IStoreHook hookAddress) public virtual {
     StoreCore.unregisterStoreHook(table, hookAddress);
   }
 }
diff --git a/packages/store/test/StoreSwitch.t.sol b/packages/store/test/StoreSwitch.t.sol
index 6ed176daee..0b6f70fb53 100644
--- a/packages/store/test/StoreSwitch.t.sol
+++ b/packages/store/test/StoreSwitch.t.sol
@@ -4,11 +4,11 @@ pragma solidity >=0.8.0;
 import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
 import { StoreCore } from "../src/StoreCore.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { StoreSwitch } from "../src/StoreSwitch.sol";
 
 // Mock Store to call MockSystem
-contract StoreSwitchTestStore is StoreReadWithStubs {
+contract StoreSwitchTestStore is StoreMock {
   MockSystem mockSystem = new MockSystem();
 
   function callViaDelegateCall() public returns (address storeAddress) {
diff --git a/packages/store/test/Vector2.t.sol b/packages/store/test/Vector2.t.sol
index d834b28971..769054a76a 100644
--- a/packages/store/test/Vector2.t.sol
+++ b/packages/store/test/Vector2.t.sol
@@ -5,11 +5,11 @@ import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
 import { Vector2, Vector2Data, Vector2TableId } from "../src/codegen/Tables.sol";
 import { StoreCore } from "../src/StoreCore.sol";
-import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../test/StoreMock.sol";
 import { FieldLayout } from "../src/FieldLayout.sol";
 import { Schema } from "../src/Schema.sol";
 
-contract Vector2Test is Test, GasReporter, StoreReadWithStubs {
+contract Vector2Test is Test, GasReporter, StoreMock {
   function testRegisterAndGetFieldLayout() public {
     startGasReport("register Vector2 field layout");
     Vector2.register();
diff --git a/packages/store/test/tables/Callbacks.t.sol b/packages/store/test/tables/Callbacks.t.sol
index 3213bdb3ee..dacf396c31 100644
--- a/packages/store/test/tables/Callbacks.t.sol
+++ b/packages/store/test/tables/Callbacks.t.sol
@@ -3,10 +3,10 @@ pragma solidity >=0.8.0;
 
 import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
-import { StoreReadWithStubs } from "../../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../../test/StoreMock.sol";
 import { Callbacks } from "../../src/codegen/Tables.sol";
 
-contract CallbacksTest is Test, GasReporter, StoreReadWithStubs {
+contract CallbacksTest is Test, GasReporter, StoreMock {
   function testSetAndGet() public {
     Callbacks.register();
     bytes32 key = keccak256("somekey");
diff --git a/packages/store/test/tables/StoreHooks.t.sol b/packages/store/test/tables/StoreHooks.t.sol
index 05d6413351..ff347c9b86 100644
--- a/packages/store/test/tables/StoreHooks.t.sol
+++ b/packages/store/test/tables/StoreHooks.t.sol
@@ -3,10 +3,10 @@ pragma solidity >=0.8.0;
 
 import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
-import { StoreReadWithStubs } from "../../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../../test/StoreMock.sol";
 import { StoreHooks } from "../../src/codegen/Tables.sol";
 
-contract StoreHooksTest is Test, GasReporter, StoreReadWithStubs {
+contract StoreHooksTest is Test, GasReporter, StoreMock {
   function testTable() public {
     // StoreHooks schema is already registered by StoreCore
     bytes32 key = keccak256("somekey");
diff --git a/packages/store/test/tables/StoreHooksColdLoad.t.sol b/packages/store/test/tables/StoreHooksColdLoad.t.sol
index fc869bcaee..07513c2c54 100644
--- a/packages/store/test/tables/StoreHooksColdLoad.t.sol
+++ b/packages/store/test/tables/StoreHooksColdLoad.t.sol
@@ -3,10 +3,10 @@ pragma solidity >=0.8.0;
 
 import { Test } from "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
-import { StoreReadWithStubs } from "../../src/StoreReadWithStubs.sol";
+import { StoreMock } from "../../test/StoreMock.sol";
 import { StoreHooks } from "../../src/codegen/Tables.sol";
 
-contract StoreHooksColdLoadTest is Test, GasReporter, StoreReadWithStubs {
+contract StoreHooksColdLoadTest is Test, GasReporter, StoreMock {
   bytes21[] hooks;
 
   function setUp() public {
diff --git a/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json b/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json
index 6053b3e357..5837ac41b0 100644
--- a/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json
+++ b/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json
@@ -117,17 +117,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -209,6 +198,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "_msgSender",
diff --git a/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json.d.ts b/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json.d.ts
index fc9e3b5c12..35b1726fb1 100644
--- a/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json.d.ts
+++ b/packages/world/abi/BalanceTransferSystem.sol/BalanceTransferSystem.abi.json.d.ts
@@ -117,17 +117,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -209,6 +198,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     inputs: [];
     name: "_msgSender";
diff --git a/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json b/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json
index 3a730bca65..37b796f074 100644
--- a/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json
+++ b/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json
@@ -117,17 +117,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -294,6 +283,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "anonymous": false,
     "inputs": [
diff --git a/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json.d.ts b/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json.d.ts
index 1991fd11ad..4f7a99f85c 100644
--- a/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json.d.ts
+++ b/packages/world/abi/CoreSystem.sol/CoreSystem.abi.json.d.ts
@@ -117,17 +117,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -294,6 +283,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     anonymous: false;
     inputs: [
diff --git a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json
index 7cb990eefd..89c084071c 100644
--- a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json
+++ b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json
@@ -96,17 +96,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -262,6 +251,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "anonymous": false,
     "inputs": [],
@@ -421,6 +415,19 @@
     "stateMutability": "payable",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "creator",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -688,6 +695,19 @@
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "contract IModule",
+        "name": "coreModule",
+        "type": "address"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [
       {
diff --git a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json.d.ts b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json.d.ts
index a1b710dd0a..de47b3b5e6 100644
--- a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json.d.ts
+++ b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json.d.ts
@@ -96,17 +96,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -262,6 +251,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     anonymous: false;
     inputs: [];
@@ -421,6 +415,19 @@ declare const abi: [
     stateMutability: "payable";
     type: "function";
   },
+  {
+    inputs: [];
+    name: "creator";
+    outputs: [
+      {
+        internalType: "address";
+        name: "";
+        type: "address";
+      }
+    ];
+    stateMutability: "view";
+    type: "function";
+  },
   {
     inputs: [
       {
@@ -688,6 +695,19 @@ declare const abi: [
     stateMutability: "nonpayable";
     type: "function";
   },
+  {
+    inputs: [
+      {
+        internalType: "contract IModule";
+        name: "coreModule";
+        type: "address";
+      }
+    ];
+    name: "initialize";
+    outputs: [];
+    stateMutability: "nonpayable";
+    type: "function";
+  },
   {
     inputs: [
       {
diff --git a/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json b/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json
index 0af520372a..06f59dc6e9 100644
--- a/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json
+++ b/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json
@@ -96,17 +96,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -139,5 +128,10 @@
     ],
     "name": "SystemExists",
     "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
   }
 ]
\ No newline at end of file
diff --git a/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json.d.ts b/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json.d.ts
index 476da1d71c..1dceca5afa 100644
--- a/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json.d.ts
+++ b/packages/world/abi/IWorldErrors.sol/IWorldErrors.abi.json.d.ts
@@ -96,17 +96,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -139,6 +128,11 @@ declare const abi: [
     ];
     name: "SystemExists";
     type: "error";
+  },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
   }
 ];
 export default abi;
diff --git a/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json b/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json
index 5c7a2545d7..d4adfc2183 100644
--- a/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json
+++ b/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json
@@ -96,17 +96,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -140,6 +129,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "anonymous": false,
     "inputs": [],
@@ -199,6 +193,32 @@
     "stateMutability": "payable",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "creator",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "contract IModule",
+        "name": "coreModule",
+        "type": "address"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [
       {
diff --git a/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json.d.ts b/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json.d.ts
index f6f89bf906..69abecf3f3 100644
--- a/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json.d.ts
+++ b/packages/world/abi/IWorldKernel.sol/IWorldKernel.abi.json.d.ts
@@ -96,17 +96,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -140,6 +129,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     anonymous: false;
     inputs: [];
@@ -199,6 +193,32 @@ declare const abi: [
     stateMutability: "payable";
     type: "function";
   },
+  {
+    inputs: [];
+    name: "creator";
+    outputs: [
+      {
+        internalType: "address";
+        name: "";
+        type: "address";
+      }
+    ];
+    stateMutability: "view";
+    type: "function";
+  },
+  {
+    inputs: [
+      {
+        internalType: "contract IModule";
+        name: "coreModule";
+        type: "address";
+      }
+    ];
+    name: "initialize";
+    outputs: [];
+    stateMutability: "nonpayable";
+    type: "function";
+  },
   {
     inputs: [
       {
diff --git a/packages/world/abi/StoreRead.sol/StoreRead.abi.json b/packages/world/abi/StoreRead.sol/StoreRead.abi.json
index 1f8dd917db..d667734d9d 100644
--- a/packages/world/abi/StoreRead.sol/StoreRead.abi.json
+++ b/packages/world/abi/StoreRead.sol/StoreRead.abi.json
@@ -1,9 +1,4 @@
 [
-  {
-    "inputs": [],
-    "stateMutability": "nonpayable",
-    "type": "constructor"
-  },
   {
     "inputs": [
       {
@@ -25,128 +20,11 @@
     "name": "FieldLayoutLib_StaticLengthIsZero",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      }
-    ],
-    "name": "SchemaLib_InvalidLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "SchemaLib_StaticTypeAfterDynamicType",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes",
-        "name": "data",
-        "type": "bytes"
-      },
-      {
-        "internalType": "uint256",
-        "name": "start",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "end",
-        "type": "uint256"
-      }
-    ],
-    "name": "Slice_OutOfBounds",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidDataLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidFieldNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidKeyNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidValueSchemaLength",
-    "type": "error"
-  },
   {
     "inputs": [],
     "name": "StoreCore_NotDynamicField",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string",
-        "name": "tableIdString",
-        "type": "string"
-      }
-    ],
-    "name": "StoreCore_TableAlreadyExists",
-    "type": "error"
-  },
   {
     "inputs": [
       {
diff --git a/packages/world/abi/StoreRead.sol/StoreRead.abi.json.d.ts b/packages/world/abi/StoreRead.sol/StoreRead.abi.json.d.ts
index 677134cc99..b55d3b54f0 100644
--- a/packages/world/abi/StoreRead.sol/StoreRead.abi.json.d.ts
+++ b/packages/world/abi/StoreRead.sol/StoreRead.abi.json.d.ts
@@ -1,9 +1,4 @@
 declare const abi: [
-  {
-    inputs: [];
-    stateMutability: "nonpayable";
-    type: "constructor";
-  },
   {
     inputs: [
       {
@@ -25,128 +20,11 @@ declare const abi: [
     name: "FieldLayoutLib_StaticLengthIsZero";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      }
-    ];
-    name: "SchemaLib_InvalidLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "SchemaLib_StaticTypeAfterDynamicType";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "bytes";
-        name: "data";
-        type: "bytes";
-      },
-      {
-        internalType: "uint256";
-        name: "start";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "end";
-        type: "uint256";
-      }
-    ];
-    name: "Slice_OutOfBounds";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidDataLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidFieldNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidKeyNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidValueSchemaLength";
-    type: "error";
-  },
   {
     inputs: [];
     name: "StoreCore_NotDynamicField";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "string";
-        name: "tableIdString";
-        type: "string";
-      }
-    ];
-    name: "StoreCore_TableAlreadyExists";
-    type: "error";
-  },
   {
     inputs: [
       {
diff --git a/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json b/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json
index d521da57d5..bf33e46ef0 100644
--- a/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json
+++ b/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json
@@ -117,17 +117,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -294,6 +283,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "_msgSender",
diff --git a/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json.d.ts b/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json.d.ts
index bb4ce29c6d..d09a81812a 100644
--- a/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json.d.ts
+++ b/packages/world/abi/StoreRegistrationSystem.sol/StoreRegistrationSystem.abi.json.d.ts
@@ -117,17 +117,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -294,6 +283,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     inputs: [];
     name: "_msgSender";
diff --git a/packages/world/abi/World.sol/World.abi.json b/packages/world/abi/World.sol/World.abi.json
index e6f9da0703..f5e0c9531a 100644
--- a/packages/world/abi/World.sol/World.abi.json
+++ b/packages/world/abi/World.sol/World.abi.json
@@ -122,17 +122,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -166,22 +155,6 @@
     "name": "ResourceNotFound",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "length",
-        "type": "uint256"
-      }
-    ],
-    "name": "SchemaLib_InvalidLength",
-    "type": "error"
-  },
-  {
-    "inputs": [],
-    "name": "SchemaLib_StaticTypeAfterDynamicType",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -235,75 +208,11 @@
     "name": "StoreCore_InvalidDataLength",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidFieldNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidKeyNamesLength",
-    "type": "error"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "uint256",
-        "name": "expected",
-        "type": "uint256"
-      },
-      {
-        "internalType": "uint256",
-        "name": "received",
-        "type": "uint256"
-      }
-    ],
-    "name": "StoreCore_InvalidValueSchemaLength",
-    "type": "error"
-  },
   {
     "inputs": [],
     "name": "StoreCore_NotDynamicField",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "bytes32",
-        "name": "tableId",
-        "type": "bytes32"
-      },
-      {
-        "internalType": "string",
-        "name": "tableIdString",
-        "type": "string"
-      }
-    ],
-    "name": "StoreCore_TableAlreadyExists",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -331,6 +240,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "anonymous": false,
     "inputs": [],
@@ -469,6 +383,19 @@
     "stateMutability": "payable",
     "type": "function"
   },
+  {
+    "inputs": [],
+    "name": "creator",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -690,6 +617,19 @@
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "contract IModule",
+        "name": "coreModule",
+        "type": "address"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [
       {
diff --git a/packages/world/abi/World.sol/World.abi.json.d.ts b/packages/world/abi/World.sol/World.abi.json.d.ts
index 733e1d2b57..63c17a18b9 100644
--- a/packages/world/abi/World.sol/World.abi.json.d.ts
+++ b/packages/world/abi/World.sol/World.abi.json.d.ts
@@ -122,17 +122,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -166,22 +155,6 @@ declare const abi: [
     name: "ResourceNotFound";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "length";
-        type: "uint256";
-      }
-    ];
-    name: "SchemaLib_InvalidLength";
-    type: "error";
-  },
-  {
-    inputs: [];
-    name: "SchemaLib_StaticTypeAfterDynamicType";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -235,75 +208,11 @@ declare const abi: [
     name: "StoreCore_InvalidDataLength";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidFieldNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidKeyNamesLength";
-    type: "error";
-  },
-  {
-    inputs: [
-      {
-        internalType: "uint256";
-        name: "expected";
-        type: "uint256";
-      },
-      {
-        internalType: "uint256";
-        name: "received";
-        type: "uint256";
-      }
-    ];
-    name: "StoreCore_InvalidValueSchemaLength";
-    type: "error";
-  },
   {
     inputs: [];
     name: "StoreCore_NotDynamicField";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "bytes32";
-        name: "tableId";
-        type: "bytes32";
-      },
-      {
-        internalType: "string";
-        name: "tableIdString";
-        type: "string";
-      }
-    ];
-    name: "StoreCore_TableAlreadyExists";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -331,6 +240,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     anonymous: false;
     inputs: [];
@@ -469,6 +383,19 @@ declare const abi: [
     stateMutability: "payable";
     type: "function";
   },
+  {
+    inputs: [];
+    name: "creator";
+    outputs: [
+      {
+        internalType: "address";
+        name: "";
+        type: "address";
+      }
+    ];
+    stateMutability: "view";
+    type: "function";
+  },
   {
     inputs: [
       {
@@ -690,6 +617,19 @@ declare const abi: [
     stateMutability: "view";
     type: "function";
   },
+  {
+    inputs: [
+      {
+        internalType: "contract IModule";
+        name: "coreModule";
+        type: "address";
+      }
+    ];
+    name: "initialize";
+    outputs: [];
+    stateMutability: "nonpayable";
+    type: "function";
+  },
   {
     inputs: [
       {
diff --git a/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json b/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json
index 8a3634a6da..c06b81b68a 100644
--- a/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json
+++ b/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json
@@ -117,17 +117,6 @@
     "name": "InvalidSelector",
     "type": "error"
   },
-  {
-    "inputs": [
-      {
-        "internalType": "string",
-        "name": "module",
-        "type": "string"
-      }
-    ],
-    "name": "ModuleAlreadyInstalled",
-    "type": "error"
-  },
   {
     "inputs": [
       {
@@ -214,6 +203,11 @@
     "name": "SystemExists",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "WorldAlreadyInitialized",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "_msgSender",
diff --git a/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json.d.ts b/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json.d.ts
index a6e9822f2f..297a309951 100644
--- a/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json.d.ts
+++ b/packages/world/abi/WorldRegistrationSystem.sol/WorldRegistrationSystem.abi.json.d.ts
@@ -117,17 +117,6 @@ declare const abi: [
     name: "InvalidSelector";
     type: "error";
   },
-  {
-    inputs: [
-      {
-        internalType: "string";
-        name: "module";
-        type: "string";
-      }
-    ];
-    name: "ModuleAlreadyInstalled";
-    type: "error";
-  },
   {
     inputs: [
       {
@@ -214,6 +203,11 @@ declare const abi: [
     name: "SystemExists";
     type: "error";
   },
+  {
+    inputs: [];
+    name: "WorldAlreadyInitialized";
+    type: "error";
+  },
   {
     inputs: [];
     name: "_msgSender";
diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json
index 6ec43db51b..4157bcf039 100644
--- a/packages/world/gas-report.json
+++ b/packages/world/gas-report.json
@@ -39,73 +39,73 @@
     "file": "test/KeysInTableModule.t.sol",
     "test": "testInstallComposite",
     "name": "install keys in table module",
-    "gasUsed": 1519694
+    "gasUsed": 1519669
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testInstallGas",
     "name": "install keys in table module",
-    "gasUsed": 1519694
+    "gasUsed": 1519669
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testInstallGas",
     "name": "set a record on a table with keysInTableModule installed",
-    "gasUsed": 183330
+    "gasUsed": 183388
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testInstallSingleton",
     "name": "install keys in table module",
-    "gasUsed": 1519694
+    "gasUsed": 1519669
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookCompositeGas",
     "name": "install keys in table module",
-    "gasUsed": 1519694
+    "gasUsed": 1519669
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookCompositeGas",
     "name": "change a composite record on a table with keysInTableModule installed",
-    "gasUsed": 28471
+    "gasUsed": 28498
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookCompositeGas",
     "name": "delete a composite record on a table with keysInTableModule installed",
-    "gasUsed": 251350
+    "gasUsed": 251457
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookGas",
     "name": "install keys in table module",
-    "gasUsed": 1519694
+    "gasUsed": 1519669
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookGas",
     "name": "change a record on a table with keysInTableModule installed",
-    "gasUsed": 27194
+    "gasUsed": 27221
   },
   {
     "file": "test/KeysInTableModule.t.sol",
     "test": "testSetAndDeleteRecordHookGas",
     "name": "delete a record on a table with keysInTableModule installed",
-    "gasUsed": 132776
+    "gasUsed": 132823
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testGetKeysWithValueGas",
     "name": "install keys with value module",
-    "gasUsed": 728670
+    "gasUsed": 728673
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testGetKeysWithValueGas",
     "name": "Get list of keys with a given value",
-    "gasUsed": 7486
+    "gasUsed": 7508
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
@@ -117,264 +117,264 @@
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testInstall",
     "name": "install keys with value module",
-    "gasUsed": 728670
+    "gasUsed": 728673
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testInstall",
     "name": "set a record on a table with KeysWithValueModule installed",
-    "gasUsed": 157267
+    "gasUsed": 157348
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetAndDeleteRecordHook",
     "name": "install keys with value module",
-    "gasUsed": 728670
+    "gasUsed": 728673
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetAndDeleteRecordHook",
     "name": "change a record on a table with KeysWithValueModule installed",
-    "gasUsed": 126277
+    "gasUsed": 126291
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetAndDeleteRecordHook",
     "name": "delete a record on a table with KeysWithValueModule installed",
-    "gasUsed": 49021
+    "gasUsed": 49099
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetField",
     "name": "install keys with value module",
-    "gasUsed": 728670
+    "gasUsed": 728673
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetField",
     "name": "set a field on a table with KeysWithValueModule installed",
-    "gasUsed": 163755
+    "gasUsed": 163792
   },
   {
     "file": "test/KeysWithValueModule.t.sol",
     "test": "testSetField",
     "name": "change a field on a table with KeysWithValueModule installed",
-    "gasUsed": 128514
+    "gasUsed": 128551
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasHasValueNotQuery",
     "name": "CombinedHasHasValueNotQuery",
-    "gasUsed": 141202
+    "gasUsed": 141196
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasHasValueQuery",
     "name": "CombinedHasHasValueQuery",
-    "gasUsed": 69011
+    "gasUsed": 69053
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasNotQuery",
     "name": "CombinedHasNotQuery",
-    "gasUsed": 184270
+    "gasUsed": 184150
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasQuery",
     "name": "CombinedHasQuery",
-    "gasUsed": 122090
+    "gasUsed": 121994
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasValueNotQuery",
     "name": "CombinedHasValueNotQuery",
-    "gasUsed": 117822
+    "gasUsed": 117772
   },
   {
     "file": "test/query.t.sol",
     "test": "testCombinedHasValueQuery",
     "name": "CombinedHasValueQuery",
-    "gasUsed": 19120
+    "gasUsed": 19164
   },
   {
     "file": "test/query.t.sol",
     "test": "testHasQuery",
     "name": "HasQuery",
-    "gasUsed": 27974
+    "gasUsed": 27950
   },
   {
     "file": "test/query.t.sol",
     "test": "testHasQuery1000Keys",
     "name": "HasQuery with 1000 keys",
-    "gasUsed": 7068034
+    "gasUsed": 7068010
   },
   {
     "file": "test/query.t.sol",
     "test": "testHasQuery100Keys",
     "name": "HasQuery with 100 keys",
-    "gasUsed": 672254
+    "gasUsed": 672230
   },
   {
     "file": "test/query.t.sol",
     "test": "testHasValueQuery",
     "name": "HasValueQuery",
-    "gasUsed": 9237
+    "gasUsed": 9259
   },
   {
     "file": "test/query.t.sol",
     "test": "testNotValueQuery",
     "name": "NotValueQuery",
-    "gasUsed": 62610
+    "gasUsed": 62652
   },
   {
     "file": "test/StandardDelegationsModule.t.sol",
     "test": "testCallFromCallboundDelegation",
     "name": "register a callbound delegation",
-    "gasUsed": 133339
+    "gasUsed": 133325
   },
   {
     "file": "test/StandardDelegationsModule.t.sol",
     "test": "testCallFromCallboundDelegation",
     "name": "call a system via a callbound delegation",
-    "gasUsed": 49400
+    "gasUsed": 49383
   },
   {
     "file": "test/StandardDelegationsModule.t.sol",
     "test": "testCallFromTimeboundDelegation",
     "name": "register a timebound delegation",
-    "gasUsed": 127695
+    "gasUsed": 127681
   },
   {
     "file": "test/StandardDelegationsModule.t.sol",
     "test": "testCallFromTimeboundDelegation",
     "name": "call a system via a timebound delegation",
-    "gasUsed": 38491
+    "gasUsed": 38474
   },
   {
     "file": "test/UniqueEntityModule.t.sol",
     "test": "testInstall",
     "name": "install unique entity module",
-    "gasUsed": 788009
+    "gasUsed": 787953
   },
   {
     "file": "test/UniqueEntityModule.t.sol",
     "test": "testInstall",
     "name": "get a unique entity nonce (non-root module)",
-    "gasUsed": 67487
+    "gasUsed": 67456
   },
   {
     "file": "test/UniqueEntityModule.t.sol",
     "test": "testInstallRoot",
     "name": "installRoot unique entity module",
-    "gasUsed": 771491
+    "gasUsed": 771494
   },
   {
     "file": "test/UniqueEntityModule.t.sol",
     "test": "testInstallRoot",
     "name": "get a unique entity nonce (root module)",
-    "gasUsed": 67487
+    "gasUsed": 67456
   },
   {
     "file": "test/World.t.sol",
     "test": "testCall",
     "name": "call a system via the World",
-    "gasUsed": 19578
+    "gasUsed": 19564
   },
   {
     "file": "test/World.t.sol",
     "test": "testCallFromUnlimitedDelegation",
     "name": "register an unlimited delegation",
-    "gasUsed": 59058
+    "gasUsed": 59044
   },
   {
     "file": "test/World.t.sol",
     "test": "testCallFromUnlimitedDelegation",
     "name": "call a system via an unlimited delegation",
-    "gasUsed": 19964
+    "gasUsed": 19950
   },
   {
     "file": "test/World.t.sol",
     "test": "testDeleteRecord",
     "name": "Delete record",
-    "gasUsed": 13858
+    "gasUsed": 13886
   },
   {
     "file": "test/World.t.sol",
     "test": "testPushToField",
     "name": "Push data to the table",
-    "gasUsed": 93235
+    "gasUsed": 93261
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterFallbackSystem",
     "name": "Register a fallback system",
-    "gasUsed": 75428
+    "gasUsed": 75414
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterFallbackSystem",
     "name": "Register a root fallback system",
-    "gasUsed": 68677
+    "gasUsed": 68663
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterFunctionSelector",
     "name": "Register a function selector",
-    "gasUsed": 96022
+    "gasUsed": 96008
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterNamespace",
     "name": "Register a new namespace",
-    "gasUsed": 146385
+    "gasUsed": 146371
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterRootFunctionSelector",
     "name": "Register a root function selector",
-    "gasUsed": 90595
+    "gasUsed": 90581
   },
   {
     "file": "test/World.t.sol",
     "test": "testRegisterTable",
     "name": "Register a new table in the namespace",
-    "gasUsed": 685351
+    "gasUsed": 685337
   },
   {
     "file": "test/World.t.sol",
     "test": "testSetField",
     "name": "Write data to a table field",
-    "gasUsed": 41938
+    "gasUsed": 41899
   },
   {
     "file": "test/World.t.sol",
     "test": "testSetRecord",
     "name": "Write data to the table",
-    "gasUsed": 41383
+    "gasUsed": 41344
   },
   {
     "file": "test/WorldDynamicUpdate.t.sol",
     "test": "testPopFromField",
     "name": "pop 1 address (cold)",
-    "gasUsed": 32936
+    "gasUsed": 32940
   },
   {
     "file": "test/WorldDynamicUpdate.t.sol",
     "test": "testPopFromField",
     "name": "pop 1 address (warm)",
-    "gasUsed": 19725
+    "gasUsed": 19729
   },
   {
     "file": "test/WorldDynamicUpdate.t.sol",
     "test": "testUpdateInField",
     "name": "updateInField 1 item (cold)",
-    "gasUsed": 35347
+    "gasUsed": 35373
   },
   {
     "file": "test/WorldDynamicUpdate.t.sol",
     "test": "testUpdateInField",
     "name": "updateInField 1 item (warm)",
-    "gasUsed": 22552
+    "gasUsed": 22578
   }
 ]
diff --git a/packages/world/src/World.sol b/packages/world/src/World.sol
index 90859ba849..9544c61608 100644
--- a/packages/world/src/World.sol
+++ b/packages/world/src/World.sol
@@ -2,6 +2,7 @@
 pragma solidity >=0.8.0;
 
 import { StoreRead } from "@latticexyz/store/src/StoreRead.sol";
+import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
 import { IStoreData } from "@latticexyz/store/src/IStore.sol";
 import { StoreCore } from "@latticexyz/store/src/StoreCore.sol";
 import { Bytes } from "@latticexyz/store/src/Bytes.sol";
@@ -29,20 +30,36 @@ import { Systems } from "./modules/core/tables/Systems.sol";
 import { SystemHooks } from "./modules/core/tables/SystemHooks.sol";
 import { FunctionSelectors } from "./modules/core/tables/FunctionSelectors.sol";
 import { Balances } from "./modules/core/tables/Balances.sol";
+import { CORE_MODULE_NAME } from "./modules/core/constants.sol";
 
 contract World is StoreRead, IStoreData, IWorldKernel {
   using ResourceSelector for bytes32;
+  address public immutable creator;
 
   constructor() {
-    // Register internal NamespaceOwner table and give ownership of the root
-    // namespace to msg.sender. This is done in the constructor instead of a
-    // module, so that we can use it for access control checks in `installRootModule`.
-    NamespaceOwner.register();
-    NamespaceOwner.set(ROOT_NAMESPACE, msg.sender);
-
+    creator = msg.sender;
+    StoreCore.initialize();
     emit HelloWorld();
   }
 
+  /**
+   * Allows the creator of the World to initialize the World once.
+   */
+  function initialize(IModule coreModule) public {
+    // Only the initial creator of the World can initialize it
+    if (msg.sender != creator) {
+      revert AccessDenied(ResourceSelector.from(ROOT_NAMESPACE).toString(), msg.sender);
+    }
+
+    // The World can only be initialized once
+    if (InstalledModules.getModuleAddress(CORE_MODULE_NAME, keccak256("")) != address(0)) {
+      revert WorldAlreadyInitialized();
+    }
+
+    // Initialize the World by installing the core module
+    _installRootModule(coreModule, new bytes(0));
+  }
+
   /**
    * Install the given root module in the World.
    * Requires the caller to own the root namespace.
@@ -50,7 +67,10 @@ contract World is StoreRead, IStoreData, IWorldKernel {
    */
   function installRootModule(IModule module, bytes memory args) public {
     AccessControl.requireOwner(ROOT_NAMESPACE, msg.sender);
+    _installRootModule(module, args);
+  }
 
+  function _installRootModule(IModule module, bytes memory args) internal {
     // Require the provided address to implement the IModule interface
     requireInterface(address(module), MODULE_INTERFACE_ID);
 
diff --git a/packages/world/src/factories/WorldFactory.sol b/packages/world/src/factories/WorldFactory.sol
index 318d225cf5..752ade4ddb 100644
--- a/packages/world/src/factories/WorldFactory.sol
+++ b/packages/world/src/factories/WorldFactory.sol
@@ -26,7 +26,7 @@ contract WorldFactory is IWorldFactory {
     IBaseWorld world = IBaseWorld(worldAddress);
 
     // Initialize the World and transfer ownership to the caller
-    world.installRootModule(coreModule, new bytes(0));
+    world.initialize(coreModule);
     world.transferOwnership(ROOT_NAMESPACE, msg.sender);
 
     emit WorldDeployed(worldAddress);
diff --git a/packages/world/src/interfaces/IWorldErrors.sol b/packages/world/src/interfaces/IWorldErrors.sol
index 3618ceb591..8804a161e9 100644
--- a/packages/world/src/interfaces/IWorldErrors.sol
+++ b/packages/world/src/interfaces/IWorldErrors.sol
@@ -2,6 +2,7 @@
 pragma solidity >=0.8.0;
 
 interface IWorldErrors {
+  error WorldAlreadyInitialized();
   error ResourceExists(string resource);
   error ResourceNotFound(string resource);
   error AccessDenied(string resource, address caller);
@@ -9,7 +10,6 @@ interface IWorldErrors {
   error SystemExists(address system);
   error FunctionSelectorExists(bytes4 functionSelector);
   error FunctionSelectorNotFound(bytes4 functionSelector);
-  error ModuleAlreadyInstalled(string module);
   error DelegationNotFound(address delegator, address delegatee);
   error InsufficientBalance(uint256 balance, uint256 amount);
   error InterfaceNotSupported(address contractAddress, bytes4 interfaceId);
diff --git a/packages/world/src/interfaces/IWorldKernel.sol b/packages/world/src/interfaces/IWorldKernel.sol
index 8cf3511269..1dc9f602ad 100644
--- a/packages/world/src/interfaces/IWorldKernel.sol
+++ b/packages/world/src/interfaces/IWorldKernel.sol
@@ -40,4 +40,14 @@ interface IWorldCall {
  */
 interface IWorldKernel is IWorldModuleInstallation, IWorldCall, IWorldErrors {
   event HelloWorld();
+
+  /**
+   * The immutable original deployer of the World.
+   */
+  function creator() external view returns (address);
+
+  /**
+   * Allows the creator of the World to initialize the World once.
+   */
+  function initialize(IModule coreModule) external;
 }
diff --git a/packages/world/src/modules/core/CoreModule.sol b/packages/world/src/modules/core/CoreModule.sol
index 5c8c660875..aaf34ff19e 100644
--- a/packages/world/src/modules/core/CoreModule.sol
+++ b/packages/world/src/modules/core/CoreModule.sol
@@ -9,6 +9,7 @@ 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 { ResourceSelector } from "../../ResourceSelector.sol";
 
 import { NamespaceOwner } from "../../tables/NamespaceOwner.sol";
@@ -63,6 +64,8 @@ contract CoreModule is Module {
    * Register core tables in the World
    */
   function _registerCoreTables() internal {
+    StoreCore.registerCoreTables();
+    NamespaceOwner.register();
     Balances.register();
     InstalledModules.register();
     Delegations.register();
@@ -73,6 +76,7 @@ contract CoreModule is Module {
     SystemRegistry.register();
     ResourceType.register();
 
+    NamespaceOwner.set(ROOT_NAMESPACE, _msgSender());
     ResourceAccess.set(ROOT_NAMESPACE, _msgSender(), true);
     ResourceType.set(ROOT_NAMESPACE, Resource.NAMESPACE);
   }
diff --git a/packages/world/test/AccessControl.t.sol b/packages/world/test/AccessControl.t.sol
index b9f571cd39..feddf64c67 100644
--- a/packages/world/test/AccessControl.t.sol
+++ b/packages/world/test/AccessControl.t.sol
@@ -3,7 +3,7 @@ pragma solidity >=0.8.0;
 
 import "forge-std/Test.sol";
 import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol";
-import { StoreReadWithStubs } from "@latticexyz/store/src/StoreReadWithStubs.sol";
+import { StoreMock } from "@latticexyz/store/test/StoreMock.sol";
 
 import { IWorldErrors } from "../src/interfaces/IWorldErrors.sol";
 import { World } from "../src/World.sol";
@@ -13,7 +13,7 @@ import { ResourceSelector } from "../src/ResourceSelector.sol";
 import { ResourceAccess } from "../src/tables/ResourceAccess.sol";
 import { NamespaceOwner } from "../src/tables/NamespaceOwner.sol";
 
-contract AccessControlTest is Test, GasReporter, StoreReadWithStubs {
+contract AccessControlTest is Test, GasReporter, StoreMock {
   bytes16 constant namespace = "namespace";
   bytes16 constant name = "name";
   address constant presetCaller = address(0x0123);
diff --git a/packages/world/test/KeysInTableModule.t.sol b/packages/world/test/KeysInTableModule.t.sol
index 3bffe5dafd..e25f7b5427 100644
--- a/packages/world/test/KeysInTableModule.t.sol
+++ b/packages/world/test/KeysInTableModule.t.sol
@@ -57,7 +57,7 @@ contract KeysInTableModuleTest is Test, GasReporter {
     singletonKeySchema = SchemaLib.encode(new SchemaType[](0));
 
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
     keyTuple1 = new bytes32[](1);
     keyTuple1[0] = key1;
     keyTuple2 = new bytes32[](1);
diff --git a/packages/world/test/KeysWithValueModule.t.sol b/packages/world/test/KeysWithValueModule.t.sol
index 708c4cd607..50307d969b 100644
--- a/packages/world/test/KeysWithValueModule.t.sol
+++ b/packages/world/test/KeysWithValueModule.t.sol
@@ -46,7 +46,7 @@ contract KeysWithValueModuleTest is Test, GasReporter {
     sourceTableSchema = SchemaEncodeHelper.encode(SchemaType.UINT256);
     sourceTableKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32);
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
     keyTuple1 = new bytes32[](1);
     keyTuple1[0] = key1;
     keyTuple2 = new bytes32[](1);
diff --git a/packages/world/test/StandardDelegationsModule.t.sol b/packages/world/test/StandardDelegationsModule.t.sol
index 9ed9a716a3..72b83642d8 100644
--- a/packages/world/test/StandardDelegationsModule.t.sol
+++ b/packages/world/test/StandardDelegationsModule.t.sol
@@ -27,7 +27,7 @@ contract StandardDelegationsModuleTest is Test, GasReporter {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
     world.installRootModule(new StandardDelegationsModule(), new bytes(0));
 
     // Register a new system
diff --git a/packages/world/test/UniqueEntityModule.t.sol b/packages/world/test/UniqueEntityModule.t.sol
index 7d8c18b4f7..22db658279 100644
--- a/packages/world/test/UniqueEntityModule.t.sol
+++ b/packages/world/test/UniqueEntityModule.t.sol
@@ -25,7 +25,7 @@ contract UniqueEntityModuleTest is Test, GasReporter {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
   }
 
   function testInstall() public {
diff --git a/packages/world/test/Utils.t.sol b/packages/world/test/Utils.t.sol
index 6a9aa2895d..de0acda0d0 100644
--- a/packages/world/test/Utils.t.sol
+++ b/packages/world/test/Utils.t.sol
@@ -25,7 +25,7 @@ contract UtilsTest is Test {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
   }
 
   function _registerAndGetNamespace(bytes16 namespace) internal returns (bytes16 returnedNamespace) {
diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol
index 95510ceeb2..640782c591 100644
--- a/packages/world/test/World.t.sol
+++ b/packages/world/test/World.t.sol
@@ -157,7 +157,7 @@ contract WorldTest is Test, GasReporter {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
 
     key = "testKey";
     keyTuple = new bytes32[](1);
@@ -177,20 +177,49 @@ contract WorldTest is Test, GasReporter {
     );
   }
 
-  function testConstructor() public {
+  function testConstructorAndInitialize() public {
+    CoreModule coreModule = new CoreModule();
+
     vm.expectEmit(true, true, true, true);
     emit HelloWorld();
-    new World();
+    IBaseWorld newWorld = IBaseWorld(address(new World()));
+
+    // Expect the creator to be the original deployer
+    assertEq(newWorld.creator(), address(this));
+
+    // Expect calls to initialize to fail if the caller is not the creator
+    vm.prank(address(0x4242));
+    vm.expectRevert(
+      abi.encodeWithSelector(
+        IWorldErrors.AccessDenied.selector,
+        ResourceSelector.from(ROOT_NAMESPACE).toString(),
+        address(0x4242)
+      )
+    );
+    newWorld.initialize(coreModule);
+
+    // Expect the creator to be able to initialize the World
+    newWorld.initialize(coreModule);
 
     // Should have registered the table data table (fka schema table)
-    assertEq(Tables.getFieldLayout(world, TablesTableId), FieldLayout.unwrap(Tables.getFieldLayout()));
-    assertEq(Tables.getAbiEncodedKeyNames(world, TablesTableId), abi.encode(Tables.getKeyNames()));
-    assertEq(Tables.getAbiEncodedFieldNames(world, TablesTableId), abi.encode(Tables.getFieldNames()));
+    assertEq(Tables.getFieldLayout(newWorld, TablesTableId), FieldLayout.unwrap(Tables.getFieldLayout()));
+    assertEq(Tables.getAbiEncodedKeyNames(newWorld, TablesTableId), abi.encode(Tables.getKeyNames()));
+    assertEq(Tables.getAbiEncodedFieldNames(newWorld, TablesTableId), abi.encode(Tables.getFieldNames()));
 
     // Should have registered the namespace owner table
-    assertEq(Tables.getFieldLayout(world, NamespaceOwnerTableId), FieldLayout.unwrap(NamespaceOwner.getFieldLayout()));
-    assertEq(Tables.getAbiEncodedKeyNames(world, NamespaceOwnerTableId), abi.encode(NamespaceOwner.getKeyNames()));
-    assertEq(Tables.getAbiEncodedFieldNames(world, NamespaceOwnerTableId), abi.encode(NamespaceOwner.getFieldNames()));
+    assertEq(
+      Tables.getFieldLayout(newWorld, NamespaceOwnerTableId),
+      FieldLayout.unwrap(NamespaceOwner.getFieldLayout())
+    );
+    assertEq(Tables.getAbiEncodedKeyNames(newWorld, NamespaceOwnerTableId), abi.encode(NamespaceOwner.getKeyNames()));
+    assertEq(
+      Tables.getAbiEncodedFieldNames(newWorld, NamespaceOwnerTableId),
+      abi.encode(NamespaceOwner.getFieldNames())
+    );
+
+    // Expect it to not be possible to initialize the World again
+    vm.expectRevert(abi.encodeWithSelector(IWorldErrors.WorldAlreadyInitialized.selector));
+    newWorld.initialize(coreModule);
   }
 
   function testRegisterModuleRevertInterfaceNotSupported() public {
diff --git a/packages/world/test/WorldBalance.t.sol b/packages/world/test/WorldBalance.t.sol
index 50c46aeb88..f7225afde6 100644
--- a/packages/world/test/WorldBalance.t.sol
+++ b/packages/world/test/WorldBalance.t.sol
@@ -31,7 +31,7 @@ contract WorldBalanceTest is Test, GasReporter {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
     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 4d385eb850..fe84fdbc75 100644
--- a/packages/world/test/WorldDynamicUpdate.t.sol
+++ b/packages/world/test/WorldDynamicUpdate.t.sol
@@ -48,7 +48,7 @@ contract UpdateInFieldTest is Test, GasReporter {
 
   function setUp() public {
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
 
     key = "testKey";
     keyTuple = new bytes32[](1);
diff --git a/packages/world/test/query.t.sol b/packages/world/test/query.t.sol
index 3a013610b9..4c4feb2176 100644
--- a/packages/world/test/query.t.sol
+++ b/packages/world/test/query.t.sol
@@ -51,7 +51,7 @@ contract QueryTest is Test, GasReporter {
     tableKeySchema = SchemaEncodeHelper.encode(SchemaType.BYTES32);
     tableValueSchema = SchemaEncodeHelper.encode(SchemaType.UINT256);
     world = IBaseWorld(address(new World()));
-    world.installRootModule(new CoreModule(), new bytes(0));
+    world.initialize(new CoreModule());
 
     key1[0] = "test1";
     key2[0] = "test2";