From a7b30c79bcc78530d2d01858de46a0fb87954fda Mon Sep 17 00:00:00 2001 From: dk1a Date: Tue, 18 Jul 2023 13:27:45 +0300 Subject: [PATCH] refactor(store,world): replace isStore with storeAddress (#1061) * refactor(store): replace isStore with storeAddress * refactor(world): replace isStore with storeAddress * rename WorldContext to WorldConsumer * gas-report * ignore storeAddress in deploy * add storeAddress to MudV2Test * fix StoreSwitch * rebuild and simplify examples * rebuild e2e * refactor MudV2Test to avoid circular dependencies * update templates * move comment about systems from StoreConsumer to WorldConsumer * use storeAddress in KeysWithValueHook for consistency * some gas optimizations * more optimizations * refactor StoreSwitch to use storage for storeAddress, exclude MudTest from abi * rebuild * cleanup * rebuild * Update packages/store/src/StoreSwitch.sol Co-authored-by: alvarius * Update packages/store/src/StoreCore.sol Co-authored-by: alvarius * Create selfish-cycles-retire.md * prettier * Update .changeset/selfish-cycles-retire.md Co-authored-by: alvarius * Update .changeset/selfish-cycles-retire.md Co-authored-by: alvarius --------- Co-authored-by: alvarius --- .changeset/selfish-cycles-retire.md | 23 +++ docs/pages/world/world-101.mdx | 8 +- e2e/packages/contracts/package.json | 1 - e2e/packages/contracts/test/Worldgen.t.sol | 4 +- .../types/ethers-contracts/IWorld.ts | 14 -- .../factories/IWorld__factory.ts | 7 - e2e/pnpm-lock.yaml | 3 - .../minimal/packages/contracts/package.json | 1 - .../contracts/test/ChatNamespaced.t.sol | 4 +- .../packages/contracts/test/CounterTest.t.sol | 12 +- .../packages/contracts/test/StructTest.t.sol | 4 +- .../types/ethers-contracts/IWorld.ts | 14 -- .../factories/IWorld__factory.ts | 7 - examples/minimal/pnpm-lock.yaml | 5 +- packages/store/abi/IStore.sol/IStore.abi.json | 7 - .../store/abi/IStore.sol/IStoreData.abi.json | 7 - .../store/abi/IStore.sol/IStoreRead.abi.json | 7 - .../MirrorSubscriber.abi.json | 173 ++++++++++++++++++ .../abi/StoreMock.sol/StoreMock.abi.json | 7 - .../abi/StoreRead.sol/StoreRead.abi.json | 7 - .../StoreReadWithStubs.abi.json | 7 - .../abi/StoreSwitch.sol/StoreSwitch.abi.json | 8 +- packages/store/gas-report.json | 92 +++++----- packages/store/package.json | 2 +- packages/store/src/IStore.sol | 5 - .../MudV2Test.t.sol => store/src/MudTest.sol} | 6 +- packages/store/src/StoreCore.sol | 6 + packages/store/src/StoreRead.sol | 4 +- packages/store/src/StoreSwitch.sol | 130 +++++++------ packages/store/test/MirrorSubscriber.sol | 34 ++++ packages/store/test/StoreCore.t.sol | 31 +--- packages/store/test/StoreCoreGas.t.sol | 31 +--- packages/store/test/StoreSwitch.t.sol | 32 ++-- .../abi/IBaseWorld.sol/IBaseWorld.abi.json | 7 - packages/world/abi/IStore.sol/IStore.abi.json | 7 - .../world/abi/IStore.sol/IStoreData.abi.json | 7 - .../world/abi/IStore.sol/IStoreRead.abi.json | 7 - .../abi/StoreRead.sol/StoreRead.abi.json | 7 - .../abi/StoreSwitch.sol/StoreSwitch.abi.json | 8 +- packages/world/abi/World.sol/World.abi.json | 7 - .../world/abi/src/IStore.sol/IStore.abi.json | 7 - .../abi/src/IStore.sol/IStoreData.abi.json | 7 - .../abi/src/IStore.sol/IStoreRead.abi.json | 7 - .../src/StoreSwitch.sol/StoreSwitch.abi.json | 8 +- packages/world/gas-report.json | 98 +++++----- packages/world/src/Utils.sol | 2 +- packages/world/src/WorldContext.sol | 2 +- .../keyswithvalue/KeysWithValueHook.sol | 13 +- .../modules/uniqueentity/getUniqueEntity.sol | 2 +- packages/world/test/World.t.sol | 21 ++- .../phaser/packages/contracts/package.json | 1 - .../packages/contracts/test/CounterTest.t.sol | 4 +- .../react/packages/contracts/package.json | 1 - .../packages/contracts/test/CounterTest.t.sol | 4 +- .../threejs/packages/contracts/package.json | 1 - .../vanilla/packages/contracts/package.json | 1 - .../packages/contracts/test/CounterTest.t.sol | 4 +- 57 files changed, 489 insertions(+), 447 deletions(-) create mode 100644 .changeset/selfish-cycles-retire.md create mode 100644 packages/store/abi/MirrorSubscriber.sol/MirrorSubscriber.abi.json rename packages/{std-contracts/src/test/MudV2Test.t.sol => store/src/MudTest.sol} (52%) create mode 100644 packages/store/test/MirrorSubscriber.sol diff --git a/.changeset/selfish-cycles-retire.md b/.changeset/selfish-cycles-retire.md new file mode 100644 index 0000000000..e00c8d3b07 --- /dev/null +++ b/.changeset/selfish-cycles-retire.md @@ -0,0 +1,23 @@ +--- +"@latticexyz/std-contracts": minor +"@latticexyz/store": minor +"@latticexyz/world": minor +--- + +Rename `MudV2Test` to `MudTest` and move from `@latticexyz/std-contracts` to `@latticexyz/store`. + +```solidity +// old import +import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +// new import +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; +``` + +Refactor `StoreSwitch` to use a storage slot instead of `function isStore()` to determine which contract is Store: + +- Previously `StoreSwitch` called `isStore()` on `msg.sender` to determine if `msg.sender` is a `Store` contract. If the call succeeded, the `Store` methods were called on `msg.sender`, otherwise the data was written to the own storage. +- With this change `StoreSwitch` instead checks for an `address` in a known storage slot. If the address equals the own address, data is written to the own storage. If it is an external address, `Store` methods are called on this address. If it is unset (`address(0)`), store methods are called on `msg.sender`. +- In practice this has the same effect as before: By default the `World` contracts sets its own address in `StoreSwitch`, while `System` contracts keep the Store address undefined, so `Systems` write to their caller (`World`) if they are executed via `call` or directly to the `World` storage if they are executed via `delegatecall`. +- Besides gas savings, this change has two additional benefits: + 1. it is now possible for `Systems` to explicitly set a `Store` address to make them exclusive to that `Store` and + 2. table libraries can now be used in tests without having to provide an explicit `Store` argument, because the `MudTest` base contract redirects reads and writes to the internal `World` contract. diff --git a/docs/pages/world/world-101.mdx b/docs/pages/world/world-101.mdx index f0233a0383..19618203cf 100644 --- a/docs/pages/world/world-101.mdx +++ b/docs/pages/world/world-101.mdx @@ -256,11 +256,11 @@ Create a new file named `MySystemTest.t.sol` in the `test` folder. pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; import { Dog } from "../src/codegen/Tables.sol"; -contract MySystemTest is MudV2Test { +contract MySystemTest is MudTest { IWorld world; function setUp() public override { @@ -273,8 +273,8 @@ contract MySystemTest is MudV2Test { // this will call the addEntry function on MySystem bytes32 key = world.addEntry("bob", "blue"); // Expect the value retrieved from the Dog at the corresponding key to match "bob" and "blue" - string memory name = Dog.getName(world, key); - string memory color = Dog.getColor(world, key); + string memory name = Dog.getName(key); + string memory color = Dog.getColor(key); assertEq(name, "bob"); assertEq(color, "blue"); } diff --git a/e2e/packages/contracts/package.json b/e2e/packages/contracts/package.json index f454a2bd9a..29a1b1bf8d 100644 --- a/e2e/packages/contracts/package.json +++ b/e2e/packages/contracts/package.json @@ -20,7 +20,6 @@ "@latticexyz/cli": "link:../../../packages/cli", "@latticexyz/config": "link:../../../packages/config", "@latticexyz/schema-type": "link:../../../packages/schema-type", - "@latticexyz/std-contracts": "link:../../../packages/std-contracts", "@latticexyz/store": "link:../../../packages/store", "@latticexyz/world": "link:../../../packages/world", "@typechain/ethers-v5": "^10.2.0", diff --git a/e2e/packages/contracts/test/Worldgen.t.sol b/e2e/packages/contracts/test/Worldgen.t.sol index 631e342644..2b2c88565e 100644 --- a/e2e/packages/contracts/test/Worldgen.t.sol +++ b/e2e/packages/contracts/test/Worldgen.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { ICustomErrorsSystem } from "../src/codegen/world/ICustomErrorsSystem.sol"; import { Position } from "../src/CustomTypes.sol"; -contract WorldgenTest is MudV2Test { +contract WorldgenTest is MudTest { function testError1WasGenerated() public { vm.expectRevert(); revert ICustomErrorsSystem.TestError1(); diff --git a/e2e/packages/contracts/types/ethers-contracts/IWorld.ts b/e2e/packages/contracts/types/ethers-contracts/IWorld.ts index 4bbea196ed..1304448c02 100644 --- a/e2e/packages/contracts/types/ethers-contracts/IWorld.ts +++ b/e2e/packages/contracts/types/ethers-contracts/IWorld.ts @@ -45,7 +45,6 @@ export interface IWorldInterface extends utils.Interface { "grantAccess(bytes16,bytes16,address)": FunctionFragment; "installModule(address,bytes)": FunctionFragment; "installRootModule(address,bytes)": FunctionFragment; - "isStore()": FunctionFragment; "pop()": FunctionFragment; "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)": FunctionFragment; "popFromField(bytes32,bytes32[],uint8,uint256)": FunctionFragment; @@ -93,7 +92,6 @@ export interface IWorldInterface extends utils.Interface { | "grantAccess" | "installModule" | "installRootModule" - | "isStore" | "pop" | "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)" | "popFromField(bytes32,bytes32[],uint8,uint256)" @@ -225,7 +223,6 @@ export interface IWorldInterface extends utils.Interface { functionFragment: "installRootModule", values: [PromiseOrValue, PromiseOrValue] ): string; - encodeFunctionData(functionFragment: "isStore", values?: undefined): string; encodeFunctionData(functionFragment: "pop", values?: undefined): string; encodeFunctionData( functionFragment: "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)", @@ -491,7 +488,6 @@ export interface IWorldInterface extends utils.Interface { functionFragment: "installRootModule", data: BytesLike ): Result; - decodeFunctionResult(functionFragment: "isStore", data: BytesLike): Result; decodeFunctionResult(functionFragment: "pop", data: BytesLike): Result; decodeFunctionResult( functionFragment: "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)", @@ -789,8 +785,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise<[void]>; - pop( overrides?: Overrides & { from?: PromiseOrValue } ): Promise; @@ -1097,8 +1091,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pop( overrides?: Overrides & { from?: PromiseOrValue } ): Promise; @@ -1405,8 +1397,6 @@ export interface IWorld extends BaseContract { overrides?: CallOverrides ): Promise; - isStore(overrides?: CallOverrides): Promise; - pop(overrides?: CallOverrides): Promise; "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)"( @@ -1758,8 +1748,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pop( overrides?: Overrides & { from?: PromiseOrValue } ): Promise; @@ -2067,8 +2055,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pop( overrides?: Overrides & { from?: PromiseOrValue } ): Promise; diff --git a/e2e/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts b/e2e/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts index 002247e9df..5caa2d7df6 100644 --- a/e2e/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts +++ b/e2e/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts @@ -717,13 +717,6 @@ const _abi = [ stateMutability: "nonpayable", type: "function", }, - { - inputs: [], - name: "isStore", - outputs: [], - stateMutability: "view", - type: "function", - }, { inputs: [], name: "pop", diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index 90e0f96c6d..7f77a23ffb 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -108,9 +108,6 @@ importers: '@latticexyz/schema-type': specifier: link:../../../packages/schema-type version: link:../../../packages/schema-type - '@latticexyz/std-contracts': - specifier: link:../../../packages/std-contracts - version: link:../../../packages/std-contracts '@latticexyz/store': specifier: link:../../../packages/store version: link:../../../packages/store diff --git a/examples/minimal/packages/contracts/package.json b/examples/minimal/packages/contracts/package.json index aefad63d21..bce37779ca 100644 --- a/examples/minimal/packages/contracts/package.json +++ b/examples/minimal/packages/contracts/package.json @@ -26,7 +26,6 @@ "@latticexyz/config": "link:../../../../packages/config", "@latticexyz/schema-type": "link:../../../../packages/schema-type", "@latticexyz/solecs": "link:../../../../packages/solecs", - "@latticexyz/std-contracts": "link:../../../../packages/std-contracts", "@latticexyz/store": "link:../../../../packages/store", "@latticexyz/world": "link:../../../../packages/world", "@solidstate/contracts": "^0.0.52", diff --git a/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol b/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol index c7dfa0ff9e..c98a25a337 100644 --- a/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol +++ b/examples/minimal/packages/contracts/test/ChatNamespaced.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { StoreCore } from "@latticexyz/store/src/StoreCore.sol"; @@ -10,7 +10,7 @@ import { IWorld } from "../src/codegen/world/IWorld.sol"; import { MessageTable, MessageTableTableId } from "../src/codegen/Tables.sol"; import { IChatNamespacedSystem } from "../src/interfaces/IChatNamespacedSystem.sol"; -contract ChatNamespacedTest is MudV2Test { +contract ChatNamespacedTest is MudTest { function testEmitEphemeral() public { bytes32[] memory keyTuple; vm.expectEmit(true, true, true, true); diff --git a/examples/minimal/packages/contracts/test/CounterTest.t.sol b/examples/minimal/packages/contracts/test/CounterTest.t.sol index 3c36c877ff..a71bc1fc4e 100644 --- a/examples/minimal/packages/contracts/test/CounterTest.t.sol +++ b/examples/minimal/packages/contracts/test/CounterTest.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; @@ -10,7 +10,7 @@ import { CounterTable, CounterTableTableId } from "../src/codegen/Tables.sol"; import { SingletonKey } from "../src/systems/IncrementSystem.sol"; -contract CounterTest is MudV2Test { +contract CounterTest is MudTest { IWorld world; function setUp() public override { @@ -30,19 +30,19 @@ contract CounterTest is MudV2Test { function testCounter() public { // Expect the counter to be 1 because it was incremented in the PostDeploy script. bytes32 key = SingletonKey; - uint32 counter = CounterTable.get(world, key); + uint32 counter = CounterTable.get(key); assertEq(counter, 1); // Expect the counter to be 2 after calling increment. world.increment(); - counter = CounterTable.get(world, key); + counter = CounterTable.get(key); assertEq(counter, 2); } function testKeysWithValue() public { bytes32 key = SingletonKey; - uint32 counter = CounterTable.get(world, key); - bytes32[] memory keysWithValue = getKeysWithValue(world, CounterTableTableId, CounterTable.encode(counter)); + uint32 counter = CounterTable.get(key); + bytes32[] memory keysWithValue = getKeysWithValue(CounterTableTableId, CounterTable.encode(counter)); assertEq(keysWithValue.length, 1); } } diff --git a/examples/minimal/packages/contracts/test/StructTest.t.sol b/examples/minimal/packages/contracts/test/StructTest.t.sol index 8b4f85a57d..d18f842a4f 100644 --- a/examples/minimal/packages/contracts/test/StructTest.t.sol +++ b/examples/minimal/packages/contracts/test/StructTest.t.sol @@ -2,14 +2,14 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; import { CounterTable, CounterTableTableId } from "../src/codegen/Tables.sol"; import { BytesStruct, StringStruct } from "../src/systems/structs.sol"; -contract StructTest is MudV2Test { +contract StructTest is MudTest { IWorld world; function setUp() public override { diff --git a/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts b/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts index 386ad0b860..a78373b57d 100644 --- a/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts +++ b/examples/minimal/packages/contracts/types/ethers-contracts/IWorld.ts @@ -56,7 +56,6 @@ export interface IWorldInterface extends utils.Interface { "increment()": FunctionFragment; "installModule(address,bytes)": FunctionFragment; "installRootModule(address,bytes)": FunctionFragment; - "isStore()": FunctionFragment; "pickUp(uint32,uint32)": FunctionFragment; "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)": FunctionFragment; "popFromField(bytes32,bytes32[],uint8,uint256)": FunctionFragment; @@ -107,7 +106,6 @@ export interface IWorldInterface extends utils.Interface { | "increment" | "installModule" | "installRootModule" - | "isStore" | "pickUp" | "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)" | "popFromField(bytes32,bytes32[],uint8,uint256)" @@ -248,7 +246,6 @@ export interface IWorldInterface extends utils.Interface { functionFragment: "installRootModule", values: [PromiseOrValue, PromiseOrValue] ): string; - encodeFunctionData(functionFragment: "isStore", values?: undefined): string; encodeFunctionData( functionFragment: "pickUp", values: [PromiseOrValue, PromiseOrValue] @@ -526,7 +523,6 @@ export interface IWorldInterface extends utils.Interface { functionFragment: "installRootModule", data: BytesLike ): Result; - decodeFunctionResult(functionFragment: "isStore", data: BytesLike): Result; decodeFunctionResult(functionFragment: "pickUp", data: BytesLike): Result; decodeFunctionResult( functionFragment: "popFromField(bytes16,bytes16,bytes32[],uint8,uint256)", @@ -847,8 +843,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise<[void]>; - pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1169,8 +1163,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1489,8 +1481,6 @@ export interface IWorld extends BaseContract { overrides?: CallOverrides ): Promise; - isStore(overrides?: CallOverrides): Promise; - pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -1856,8 +1846,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, @@ -2179,8 +2167,6 @@ export interface IWorld extends BaseContract { overrides?: Overrides & { from?: PromiseOrValue } ): Promise; - isStore(overrides?: CallOverrides): Promise; - pickUp( item: PromiseOrValue, itemVariant: PromiseOrValue, diff --git a/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts b/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts index 65d99da0a2..4edb824fcf 100644 --- a/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts +++ b/examples/minimal/packages/contracts/types/ethers-contracts/factories/IWorld__factory.ts @@ -732,13 +732,6 @@ const _abi = [ stateMutability: "nonpayable", type: "function", }, - { - inputs: [], - name: "isStore", - outputs: [], - stateMutability: "view", - type: "function", - }, { inputs: [ { diff --git a/examples/minimal/pnpm-lock.yaml b/examples/minimal/pnpm-lock.yaml index b09cac4911..f0f42bdf4e 100644 --- a/examples/minimal/pnpm-lock.yaml +++ b/examples/minimal/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -356,9 +356,6 @@ importers: '@latticexyz/solecs': specifier: link:../../../../packages/solecs version: link:../../../../packages/solecs - '@latticexyz/std-contracts': - specifier: link:../../../../packages/std-contracts - version: link:../../../../packages/std-contracts '@latticexyz/store': specifier: link:../../../../packages/store version: link:../../../../packages/store diff --git a/packages/store/abi/IStore.sol/IStore.abi.json b/packages/store/abi/IStore.sol/IStore.abi.json index fa4b3d868f..9af8379342 100644 --- a/packages/store/abi/IStore.sol/IStore.abi.json +++ b/packages/store/abi/IStore.sol/IStore.abi.json @@ -428,13 +428,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/store/abi/IStore.sol/IStoreData.abi.json b/packages/store/abi/IStore.sol/IStoreData.abi.json index 44c5939df8..3d3e52c5c6 100644 --- a/packages/store/abi/IStore.sol/IStoreData.abi.json +++ b/packages/store/abi/IStore.sol/IStoreData.abi.json @@ -290,13 +290,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/store/abi/IStore.sol/IStoreRead.abi.json b/packages/store/abi/IStore.sol/IStoreRead.abi.json index 182dfafa10..e6955707bb 100644 --- a/packages/store/abi/IStore.sol/IStoreRead.abi.json +++ b/packages/store/abi/IStore.sol/IStoreRead.abi.json @@ -196,12 +196,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" } ] \ No newline at end of file diff --git a/packages/store/abi/MirrorSubscriber.sol/MirrorSubscriber.abi.json b/packages/store/abi/MirrorSubscriber.sol/MirrorSubscriber.abi.json new file mode 100644 index 0000000000..0017c899a7 --- /dev/null +++ b/packages/store/abi/MirrorSubscriber.sol/MirrorSubscriber.abi.json @@ -0,0 +1,173 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "table", + "type": "bytes32" + }, + { + "internalType": "Schema", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "Schema", + "name": "keySchema", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "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": "bytes32", + "name": "tableId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "tableIdString", + "type": "string" + } + ], + "name": "StoreCore_TableNotFound", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + }, + { + "internalType": "uint8", + "name": "", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onAfterSetField", + "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": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onBeforeSetField", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "table", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "key", + "type": "bytes32[]" + } + ], + "name": "onDeleteRecord", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "table", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "key", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onSetRecord", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/store/abi/StoreMock.sol/StoreMock.abi.json b/packages/store/abi/StoreMock.sol/StoreMock.abi.json index b6a0214721..dcf872c81f 100644 --- a/packages/store/abi/StoreMock.sol/StoreMock.abi.json +++ b/packages/store/abi/StoreMock.sol/StoreMock.abi.json @@ -465,13 +465,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/store/abi/StoreRead.sol/StoreRead.abi.json b/packages/store/abi/StoreRead.sol/StoreRead.abi.json index d3224ec101..3e8785719d 100644 --- a/packages/store/abi/StoreRead.sol/StoreRead.abi.json +++ b/packages/store/abi/StoreRead.sol/StoreRead.abi.json @@ -307,12 +307,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" } ] \ No newline at end of file diff --git a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json b/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json index 14a409eab1..380d5e6255 100644 --- a/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json +++ b/packages/store/abi/StoreReadWithStubs.sol/StoreReadWithStubs.abi.json @@ -470,13 +470,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/store/abi/StoreSwitch.sol/StoreSwitch.abi.json b/packages/store/abi/StoreSwitch.sol/StoreSwitch.abi.json index 613386a47c..0637a088a0 100644 --- a/packages/store/abi/StoreSwitch.sol/StoreSwitch.abi.json +++ b/packages/store/abi/StoreSwitch.sol/StoreSwitch.abi.json @@ -1,7 +1 @@ -[ - { - "inputs": [], - "name": "StoreSwitch_InvalidInsideConstructor", - "type": "error" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/packages/store/gas-report.json b/packages/store/gas-report.json index 3cb96c0ad8..7993745630 100644 --- a/packages/store/gas-report.json +++ b/packages/store/gas-report.json @@ -99,7 +99,7 @@ "file": "test/KeyEncoding.t.sol", "test": "testRegisterAndGetSchema", "name": "register KeyEncoding schema", - "gasUsed": 63192 + "gasUsed": 64679 }, { "file": "test/Mixed.t.sol", @@ -111,19 +111,19 @@ "file": "test/Mixed.t.sol", "test": "testRegisterAndGetSchema", "name": "register Mixed schema", - "gasUsed": 60210 + "gasUsed": 61687 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "set record in Mixed", - "gasUsed": 112166 + "gasUsed": 111108 }, { "file": "test/Mixed.t.sol", "test": "testSetAndGet", "name": "get record from Mixed", - "gasUsed": 13135 + "gasUsed": 12600 }, { "file": "test/PackedCounter.t.sol", @@ -339,25 +339,25 @@ "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (cold, 1 slot, 1 uint32 item)", - "gasUsed": 29294 + "gasUsed": 30792 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromSecondField", "name": "pop from field (warm, 1 slot, 1 uint32 item)", - "gasUsed": 19349 + "gasUsed": 18847 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (cold, 2 slots, 10 uint32 items)", - "gasUsed": 31177 + "gasUsed": 32675 }, { "file": "test/StoreCoreDynamic.t.sol", "test": "testPopFromThirdField", "name": "pop from field (warm, 2 slots, 10 uint32 items)", - "gasUsed": 19232 + "gasUsed": 18730 }, { "file": "test/StoreCoreGas.t.sol", @@ -393,7 +393,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testDeleteData", "name": "delete record (complex data, 3 slots)", - "gasUsed": 10994 + "gasUsed": 10447 }, { "file": "test/StoreCoreGas.t.sol", @@ -411,61 +411,61 @@ "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "register subscriber", - "gasUsed": 65560 + "gasUsed": 66466 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set record on table with subscriber", - "gasUsed": 73961 + "gasUsed": 74409 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "set static field on table with subscriber", - "gasUsed": 31943 + "gasUsed": 30350 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooks", "name": "delete record on table with subscriber", - "gasUsed": 24585 + "gasUsed": 23011 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "register subscriber", - "gasUsed": 65560 + "gasUsed": 66466 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) record on table with subscriber", - "gasUsed": 167398 + "gasUsed": 167846 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "set (dynamic) field on table with subscriber", - "gasUsed": 35081 + "gasUsed": 33500 }, { "file": "test/StoreCoreGas.t.sol", "test": "testHooksDynamicData", "name": "delete (dynamic) record on table with subscriber", - "gasUsed": 26057 + "gasUsed": 24483 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (1 slot, 1 uint32 item)", - "gasUsed": 17065 + "gasUsed": 16518 }, { "file": "test/StoreCoreGas.t.sol", "test": "testPushToField", "name": "push to field (2 slots, 10 uint32 items)", - "gasUsed": 39781 + "gasUsed": 39234 }, { "file": "test/StoreCoreGas.t.sol", @@ -489,7 +489,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetDynamicData", "name": "set complex record with dynamic data (4 slots)", - "gasUsed": 107707 + "gasUsed": 109160 }, { "file": "test/StoreCoreGas.t.sol", @@ -531,7 +531,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (1 slot)", - "gasUsed": 37925 + "gasUsed": 39378 }, { "file": "test/StoreCoreGas.t.sol", @@ -543,7 +543,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set static field (overlap 2 slot)", - "gasUsed": 34887 + "gasUsed": 34340 }, { "file": "test/StoreCoreGas.t.sol", @@ -555,7 +555,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, first dynamic field)", - "gasUsed": 57386 + "gasUsed": 56839 }, { "file": "test/StoreCoreGas.t.sol", @@ -567,7 +567,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetField", "name": "set dynamic field (1 slot, second dynamic field)", - "gasUsed": 35518 + "gasUsed": 34971 }, { "file": "test/StoreCoreGas.t.sol", @@ -579,7 +579,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticData", "name": "set static record (1 slot)", - "gasUsed": 37374 + "gasUsed": 38827 }, { "file": "test/StoreCoreGas.t.sol", @@ -591,7 +591,7 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetAndGetStaticDataSpanningWords", "name": "set static record (2 slots)", - "gasUsed": 59942 + "gasUsed": 61395 }, { "file": "test/StoreCoreGas.t.sol", @@ -603,79 +603,79 @@ "file": "test/StoreCoreGas.t.sol", "test": "testSetMetadata", "name": "StoreCore: set table metadata", - "gasUsed": 251866 + "gasUsed": 252784 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "update in field (1 slot, 1 uint32 item)", - "gasUsed": 16611 + "gasUsed": 16064 }, { "file": "test/StoreCoreGas.t.sol", "test": "testUpdateInField", "name": "push to field (2 slots, 6 uint64 items)", - "gasUsed": 17702 + "gasUsed": 17155 }, { "file": "test/StoreMetadata.t.sol", "test": "testSetAndGet", "name": "set record in StoreMetadataTable", - "gasUsed": 250181 + "gasUsed": 251257 }, { "file": "test/StoreMetadata.t.sol", "test": "testSetAndGet", "name": "get record from StoreMetadataTable", - "gasUsed": 12486 + "gasUsed": 12018 }, { "file": "test/StoreSwitch.t.sol", - "test": "testIsDelegatecall", - "name": "check if delegatecall", - "gasUsed": 683 + "test": "testDelegatecall", + "name": "get Store address", + "gasUsed": 2170 }, { "file": "test/StoreSwitch.t.sol", - "test": "testIsNoDelegatecall", - "name": "check if delegatecall", - "gasUsed": 703 + "test": "testNoDelegatecall", + "name": "get Store address", + "gasUsed": 2173 }, { "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "set field in Callbacks", - "gasUsed": 63203 + "gasUsed": 62243 }, { "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "get field from Callbacks (warm)", - "gasUsed": 5785 + "gasUsed": 5305 }, { "file": "test/tables/Callbacks.t.sol", "test": "testSetAndGet", "name": "push field to Callbacks", - "gasUsed": 40890 + "gasUsed": 39931 }, { "file": "test/tables/Hooks.t.sol", "test": "testSetAndGet", "name": "set field in Hooks", - "gasUsed": 63360 + "gasUsed": 64400 }, { "file": "test/tables/Hooks.t.sol", "test": "testSetAndGet", "name": "get field from Hooks (warm)", - "gasUsed": 5935 + "gasUsed": 5455 }, { "file": "test/tables/Hooks.t.sol", "test": "testSetAndGet", "name": "push field to Hooks", - "gasUsed": 40881 + "gasUsed": 39922 }, { "file": "test/tightcoder/DecodeSlice.t.sol", @@ -735,18 +735,18 @@ "file": "test/Vector2.t.sol", "test": "testRegisterAndGetSchema", "name": "register Vector2 schema", - "gasUsed": 57391 + "gasUsed": 58890 }, { "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "set Vector2 record", - "gasUsed": 38660 + "gasUsed": 37646 }, { "file": "test/Vector2.t.sol", "test": "testSetAndGet", "name": "get Vector2 record", - "gasUsed": 4970 + "gasUsed": 4457 } ] diff --git a/packages/store/package.json b/packages/store/package.json index 59a821c7de..6373328ad6 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -35,7 +35,7 @@ }, "scripts": { "build": "pnpm run build:mud && pnpm run build:abi && pnpm run build:typechain && pnpm run build:js", - "build:abi": "forge build --extra-output-files abi --out abi --skip test script", + "build:abi": "forge build --extra-output-files abi --out abi --skip test script MudTest.sol", "build:js": "tsup", "build:mud": "tsx ./ts/scripts/tablegen.ts", "build:typechain": "typechain --target ethers-v5 'abi/**/*.sol/!(*.abi).json'", diff --git a/packages/store/src/IStore.sol b/packages/store/src/IStore.sol index d76cfe604d..9bec85453c 100644 --- a/packages/store/src/IStore.sol +++ b/packages/store/src/IStore.sol @@ -35,11 +35,6 @@ interface IStoreRead { uint256 start, uint256 end ) external view returns (bytes memory data); - - // If this function exists on the contract, it is a store - // TODO: benchmark this vs. using a known storage slot to determine whether a contract is a Store - // (see https://github.com/latticexyz/mud/issues/444) - function isStore() external view; } interface IStoreWrite { diff --git a/packages/std-contracts/src/test/MudV2Test.t.sol b/packages/store/src/MudTest.sol similarity index 52% rename from packages/std-contracts/src/test/MudV2Test.t.sol rename to packages/store/src/MudTest.sol index 0bcc2e4c15..031f44c861 100644 --- a/packages/std-contracts/src/test/MudV2Test.t.sol +++ b/packages/store/src/MudTest.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; +import { StoreSwitch } from "../src/StoreSwitch.sol"; -contract MudV2Test is Test { +contract MudTest is Test { address worldAddress; function setUp() public virtual { worldAddress = vm.parseAddress(vm.readFile(".mudtest")); + StoreSwitch.setStoreAddress(worldAddress); } } diff --git a/packages/store/src/StoreCore.sol b/packages/store/src/StoreCore.sol index bf5e7afbcc..af8a8c6b71 100644 --- a/packages/store/src/StoreCore.sol +++ b/packages/store/src/StoreCore.sol @@ -12,6 +12,7 @@ import { StoreMetadata, Hooks, HooksTableId } from "./codegen/Tables.sol"; import { IStoreErrors } from "./IStoreErrors.sol"; import { IStoreHook } from "./IStore.sol"; import { TableId } from "./TableId.sol"; +import { StoreSwitch } from "./StoreSwitch.sol"; library StoreCore { using TableId for bytes32; @@ -29,6 +30,11 @@ library StoreCore { * (see https://github.com/latticexyz/mud/issues/444) */ function initialize() internal { + // StoreSwitch uses the storeAddress to decide where to write data to. + // 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 schema table registerSchema( StoreCoreInternal.SCHEMA_TABLE, diff --git a/packages/store/src/StoreRead.sol b/packages/store/src/StoreRead.sol index f1b5ea9ca6..c668816d2f 100644 --- a/packages/store/src/StoreRead.sol +++ b/packages/store/src/StoreRead.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -import { IStoreRead, IStoreHook } from "./IStore.sol"; +import { IStoreRead } from "./IStore.sol"; import { StoreCore } from "./StoreCore.sol"; import { Schema } from "./Schema.sol"; @@ -60,6 +60,4 @@ contract StoreRead is IStoreRead { ) public view virtual returns (bytes memory) { return StoreCore.getFieldSlice(tableId, key, schemaIndex, schema, start, end); } - - function isStore() public view {} } diff --git a/packages/store/src/StoreSwitch.sol b/packages/store/src/StoreSwitch.sol index 629cb5c08d..1681d1c64e 100644 --- a/packages/store/src/StoreSwitch.sol +++ b/packages/store/src/StoreSwitch.sol @@ -9,103 +9,119 @@ import { Schema } from "./Schema.sol"; * Call IStore functions on self or msg.sender, depending on whether the call is a delegatecall or regular call. */ library StoreSwitch { - error StoreSwitch_InvalidInsideConstructor(); + bytes32 private constant STORAGE_SLOT = keccak256("mud.store.storage.StoreSwitch"); - /** - * Detect whether the current call is a delegatecall or regular call. - * (The isStore method doesn't return a value to save gas, but it if exists, the call will succeed.) - */ - function isDelegateCall() internal view returns (bool success) { - // Detect calls from within a constructor - uint256 codeSize; + struct StorageSlotLayout { + address storeAddress; + } + + function _layout() private pure returns (StorageSlotLayout storage layout) { + bytes32 slot = STORAGE_SLOT; assembly { - codeSize := extcodesize(address()) + layout.slot := slot } + } - // If the call is from within a constructor, use StoreCore to write to own storage - if (codeSize == 0) return true; - - // Check whether this contract implements the IStore interface - try IStore(address(this)).isStore() { - success = true; - } catch { - success = false; + /** + * Get the Store address for use by other StoreSwitch functions. + * 0x00 is a magic number for msg.sender + * (which means that uninitialized storeAddress is msg.sender by default) + */ + function getStoreAddress() internal view returns (address) { + address _storeAddress = _layout().storeAddress; + if (_storeAddress == address(0)) { + return msg.sender; + } else { + return _storeAddress; } } - function inferStoreAddress() internal view returns (address) { - return isDelegateCall() ? address(this) : msg.sender; + /** + * Set the Store address for use by other StoreSwitch functions. + * If it stays uninitialized, StoreSwitch falls back to calling store methods on msg.sender. + */ + function setStoreAddress(address _storeAddress) internal { + _layout().storeAddress = _storeAddress; } function registerStoreHook(bytes32 table, IStoreHook hook) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.registerStoreHook(table, hook); } else { - IStore(msg.sender).registerStoreHook(table, hook); + IStore(_storeAddress).registerStoreHook(table, hook); } } function getSchema(bytes32 table) internal view returns (Schema schema) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { schema = StoreCore.getSchema(table); } else { - schema = IStore(msg.sender).getSchema(table); + schema = IStore(_storeAddress).getSchema(table); } } function getKeySchema(bytes32 table) internal view returns (Schema keySchema) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { keySchema = StoreCore.getKeySchema(table); } else { - keySchema = IStore(msg.sender).getKeySchema(table); + keySchema = IStore(_storeAddress).getKeySchema(table); } } function setMetadata(bytes32 table, string memory tableName, string[] memory fieldNames) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.setMetadata(table, tableName, fieldNames); } else { - IStore(msg.sender).setMetadata(table, tableName, fieldNames); + IStore(_storeAddress).setMetadata(table, tableName, fieldNames); } } function registerSchema(bytes32 table, Schema schema, Schema keySchema) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.registerSchema(table, schema, keySchema); } else { - IStore(msg.sender).registerSchema(table, schema, keySchema); + IStore(_storeAddress).registerSchema(table, schema, keySchema); } } function setRecord(bytes32 table, bytes32[] memory key, bytes memory data) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.setRecord(table, key, data); } else { - IStore(msg.sender).setRecord(table, key, data); + IStore(_storeAddress).setRecord(table, key, data); } } function setField(bytes32 table, bytes32[] memory key, uint8 fieldIndex, bytes memory data) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.setField(table, key, fieldIndex, data); } else { - IStore(msg.sender).setField(table, key, fieldIndex, data); + IStore(_storeAddress).setField(table, key, fieldIndex, data); } } function pushToField(bytes32 table, bytes32[] memory key, uint8 fieldIndex, bytes memory dataToPush) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.pushToField(table, key, fieldIndex, dataToPush); } else { - IStore(msg.sender).pushToField(table, key, fieldIndex, dataToPush); + IStore(_storeAddress).pushToField(table, key, fieldIndex, dataToPush); } } function popFromField(bytes32 table, bytes32[] memory key, uint8 fieldIndex, uint256 byteLengthToPop) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.popFromField(table, key, fieldIndex, byteLengthToPop); } else { - IStore(msg.sender).popFromField(table, key, fieldIndex, byteLengthToPop); + IStore(_storeAddress).popFromField(table, key, fieldIndex, byteLengthToPop); } } @@ -116,50 +132,56 @@ library StoreSwitch { uint256 startByteIndex, bytes memory dataToSet ) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.updateInField(table, key, fieldIndex, startByteIndex, dataToSet); } else { - IStore(msg.sender).updateInField(table, key, fieldIndex, startByteIndex, dataToSet); + IStore(_storeAddress).updateInField(table, key, fieldIndex, startByteIndex, dataToSet); } } function deleteRecord(bytes32 table, bytes32[] memory key) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.deleteRecord(table, key); } else { - IStore(msg.sender).deleteRecord(table, key); + IStore(_storeAddress).deleteRecord(table, key); } } function emitEphemeralRecord(bytes32 table, bytes32[] memory key, bytes memory data) internal { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { StoreCore.emitEphemeralRecord(table, key, data); } else { - IStore(msg.sender).emitEphemeralRecord(table, key, data); + IStore(_storeAddress).emitEphemeralRecord(table, key, data); } } function getRecord(bytes32 table, bytes32[] memory key) internal view returns (bytes memory) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { return StoreCore.getRecord(table, key); } else { - return IStore(msg.sender).getRecord(table, key); + return IStore(_storeAddress).getRecord(table, key); } } function getRecord(bytes32 table, bytes32[] memory key, Schema schema) internal view returns (bytes memory) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { return StoreCore.getRecord(table, key, schema); } else { - return IStore(msg.sender).getRecord(table, key, schema); + return IStore(_storeAddress).getRecord(table, key, schema); } } function getField(bytes32 table, bytes32[] memory key, uint8 fieldIndex) internal view returns (bytes memory) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { return StoreCore.getField(table, key, fieldIndex); } else { - return IStore(msg.sender).getField(table, key, fieldIndex); + return IStore(_storeAddress).getField(table, key, fieldIndex); } } @@ -169,10 +191,11 @@ library StoreSwitch { uint8 fieldIndex, Schema schema ) internal view returns (uint256) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { return StoreCore.getFieldLength(table, key, fieldIndex, schema); } else { - return IStore(msg.sender).getFieldLength(table, key, fieldIndex, schema); + return IStore(_storeAddress).getFieldLength(table, key, fieldIndex, schema); } } @@ -184,10 +207,11 @@ library StoreSwitch { uint256 start, uint256 end ) internal view returns (bytes memory) { - if (isDelegateCall()) { + address _storeAddress = getStoreAddress(); + if (_storeAddress == address(this)) { return StoreCore.getFieldSlice(table, key, fieldIndex, schema, start, end); } else { - return IStore(msg.sender).getFieldSlice(table, key, fieldIndex, schema, start, end); + return IStore(_storeAddress).getFieldSlice(table, key, fieldIndex, schema, start, end); } } } diff --git a/packages/store/test/MirrorSubscriber.sol b/packages/store/test/MirrorSubscriber.sol new file mode 100644 index 0000000000..66ee093272 --- /dev/null +++ b/packages/store/test/MirrorSubscriber.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { IStore, IStoreHook } from "../src/IStore.sol"; +import { StoreSwitch } from "../src/StoreSwitch.sol"; +import { Schema } from "../src/Schema.sol"; + +bytes32 constant indexerTableId = keccak256("indexer.table"); + +contract MirrorSubscriber is IStoreHook { + bytes32 _table; + + constructor(bytes32 table, Schema schema, Schema keySchema) { + IStore(msg.sender).registerSchema(indexerTableId, schema, keySchema); + _table = table; + } + + function onSetRecord(bytes32 table, bytes32[] memory key, bytes memory data) public { + if (table != table) revert("invalid table"); + StoreSwitch.setRecord(indexerTableId, key, data); + } + + function onBeforeSetField(bytes32 table, bytes32[] memory key, uint8 schemaIndex, bytes memory data) public { + if (table != table) revert("invalid table"); + StoreSwitch.setField(indexerTableId, key, schemaIndex, data); + } + + function onAfterSetField(bytes32, bytes32[] memory, uint8, bytes memory) public {} + + function onDeleteRecord(bytes32 table, bytes32[] memory key) public { + if (table != table) revert("invalid table"); + StoreSwitch.deleteRecord(indexerTableId, key); + } +} diff --git a/packages/store/test/StoreCore.t.sol b/packages/store/test/StoreCore.t.sol index 95d440813c..613c7914ff 100644 --- a/packages/store/test/StoreCore.t.sol +++ b/packages/store/test/StoreCore.t.sol @@ -12,10 +12,11 @@ import { Schema, SchemaLib } from "../src/Schema.sol"; import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol"; import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol"; import { IStoreErrors } from "../src/IStoreErrors.sol"; -import { IStore, IStoreHook } from "../src/IStore.sol"; +import { IStore } from "../src/IStore.sol"; import { StoreSwitch } from "../src/StoreSwitch.sol"; import { StoreMetadataData, StoreMetadata } from "../src/codegen/Tables.sol"; import { StoreMock } from "./StoreMock.sol"; +import { MirrorSubscriber, indexerTableId } from "./MirrorSubscriber.sol"; struct TestStruct { uint128 firstData; @@ -839,31 +840,3 @@ contract StoreCoreTest is Test, StoreMock { assertEq(keccak256(indexedData), keccak256(abi.encodePacked(bytes16(0)))); } } - -bytes32 constant indexerTableId = keccak256("indexer.table"); - -contract MirrorSubscriber is IStoreHook { - bytes32 _table; - - constructor(bytes32 table, Schema schema, Schema keySchema) { - IStore(msg.sender).registerSchema(indexerTableId, schema, keySchema); - _table = table; - } - - function onSetRecord(bytes32 table, bytes32[] memory key, bytes memory data) public { - if (table != table) revert("invalid table"); - StoreSwitch.setRecord(indexerTableId, key, data); - } - - function onBeforeSetField(bytes32 table, bytes32[] memory key, uint8 schemaIndex, bytes memory data) public { - if (table != table) revert("invalid table"); - StoreSwitch.setField(indexerTableId, key, schemaIndex, data); - } - - function onAfterSetField(bytes32, bytes32[] memory, uint8, bytes memory) public {} - - function onDeleteRecord(bytes32 table, bytes32[] memory key) public { - if (table != table) revert("invalid table"); - StoreSwitch.deleteRecord(indexerTableId, key); - } -} diff --git a/packages/store/test/StoreCoreGas.t.sol b/packages/store/test/StoreCoreGas.t.sol index 2ac1348de8..8bb60b01fb 100644 --- a/packages/store/test/StoreCoreGas.t.sol +++ b/packages/store/test/StoreCoreGas.t.sol @@ -12,10 +12,11 @@ import { Schema, SchemaLib } from "../src/Schema.sol"; import { PackedCounter, PackedCounterLib } from "../src/PackedCounter.sol"; import { StoreReadWithStubs } from "../src/StoreReadWithStubs.sol"; import { IStoreErrors } from "../src/IStoreErrors.sol"; -import { IStore, IStoreHook } from "../src/IStore.sol"; +import { IStore } from "../src/IStore.sol"; import { StoreSwitch } from "../src/StoreSwitch.sol"; import { StoreMetadataData, StoreMetadata } from "../src/codegen/Tables.sol"; import { StoreMock } from "./StoreMock.sol"; +import { MirrorSubscriber } from "./MirrorSubscriber.sol"; struct TestStruct { uint128 firstData; @@ -639,31 +640,3 @@ contract StoreCoreGasTest is Test, GasReporter, StoreMock { endGasReport(); } } - -bytes32 constant indexerTableId = keccak256("indexer.table"); - -contract MirrorSubscriber is IStoreHook { - bytes32 _table; - - constructor(bytes32 table, Schema schema, Schema keySchema) { - IStore(msg.sender).registerSchema(indexerTableId, schema, keySchema); - _table = table; - } - - function onSetRecord(bytes32 table, bytes32[] memory key, bytes memory data) public { - if (table != table) revert("invalid table"); - StoreSwitch.setRecord(indexerTableId, key, data); - } - - function onBeforeSetField(bytes32 table, bytes32[] memory key, uint8 schemaIndex, bytes memory data) public { - if (table != table) revert("invalid table"); - StoreSwitch.setField(indexerTableId, key, schemaIndex, data); - } - - function onAfterSetField(bytes32, bytes32[] memory, uint8, bytes memory) public {} - - function onDeleteRecord(bytes32 table, bytes32[] memory key) public { - if (table != table) revert("invalid table"); - StoreSwitch.deleteRecord(indexerTableId, key); - } -} diff --git a/packages/store/test/StoreSwitch.t.sol b/packages/store/test/StoreSwitch.t.sol index 12b25fb931..6ed176daee 100644 --- a/packages/store/test/StoreSwitch.t.sol +++ b/packages/store/test/StoreSwitch.t.sol @@ -11,24 +11,24 @@ import { StoreSwitch } from "../src/StoreSwitch.sol"; contract StoreSwitchTestStore is StoreReadWithStubs { MockSystem mockSystem = new MockSystem(); - function callViaDelegateCall() public returns (bool isDelegate) { - (bool success, bytes memory data) = address(mockSystem).delegatecall(abi.encodeWithSignature("isDelegateCall()")); + function callViaDelegateCall() public returns (address storeAddress) { + (bool success, bytes memory data) = address(mockSystem).delegatecall(abi.encodeWithSignature("getStoreAddress()")); if (!success) revert("delegatecall failed"); - isDelegate = abi.decode(data, (bool)); + storeAddress = abi.decode(data, (address)); } - function callViaCall() public returns (bool isDelegate) { - (bool success, bytes memory data) = address(mockSystem).call(abi.encodeWithSignature("isDelegateCall()")); + function callViaCall() public returns (address storeAddress) { + (bool success, bytes memory data) = address(mockSystem).call(abi.encodeWithSignature("getStoreAddress()")); if (!success) revert("delegatecall failed"); - isDelegate = abi.decode(data, (bool)); + storeAddress = abi.decode(data, (address)); } } -// Mock system to wrap StoreSwitch.isDelegateCall() +// Mock system to wrap StoreSwitch.getStoreAddress() contract MockSystem is GasReporter { - function isDelegateCall() public returns (bool isDelegate) { - startGasReport("check if delegatecall"); - isDelegate = StoreSwitch.isDelegateCall(); + function getStoreAddress() public returns (address storeAddress) { + startGasReport("get Store address"); + storeAddress = StoreSwitch.getStoreAddress(); endGasReport(); } } @@ -40,14 +40,14 @@ contract StoreSwitchTest is Test, GasReporter { store = new StoreSwitchTestStore(); } - function testIsDelegatecall() public { - bool isDelegate = store.callViaDelegateCall(); - assertTrue(isDelegate); + function testDelegatecall() public { + address storeAddress = store.callViaDelegateCall(); + assertEq(storeAddress, address(store)); } - function testIsNoDelegatecall() public { - bool isDelegate = store.callViaCall(); - assertFalse(isDelegate); + function testNoDelegatecall() public { + address storeAddress = store.callViaCall(); + assertEq(storeAddress, address(store)); } // TODO: tests for setting data on self vs msg.sender diff --git a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json index 665470de35..64e76c27d5 100644 --- a/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json +++ b/packages/world/abi/IBaseWorld.sol/IBaseWorld.abi.json @@ -666,13 +666,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/IStore.sol/IStore.abi.json b/packages/world/abi/IStore.sol/IStore.abi.json index fa4b3d868f..9af8379342 100644 --- a/packages/world/abi/IStore.sol/IStore.abi.json +++ b/packages/world/abi/IStore.sol/IStore.abi.json @@ -428,13 +428,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/IStore.sol/IStoreData.abi.json b/packages/world/abi/IStore.sol/IStoreData.abi.json index 44c5939df8..3d3e52c5c6 100644 --- a/packages/world/abi/IStore.sol/IStoreData.abi.json +++ b/packages/world/abi/IStore.sol/IStoreData.abi.json @@ -290,13 +290,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/IStore.sol/IStoreRead.abi.json b/packages/world/abi/IStore.sol/IStoreRead.abi.json index 182dfafa10..e6955707bb 100644 --- a/packages/world/abi/IStore.sol/IStoreRead.abi.json +++ b/packages/world/abi/IStore.sol/IStoreRead.abi.json @@ -196,12 +196,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" } ] \ No newline at end of file diff --git a/packages/world/abi/StoreRead.sol/StoreRead.abi.json b/packages/world/abi/StoreRead.sol/StoreRead.abi.json index d3224ec101..3e8785719d 100644 --- a/packages/world/abi/StoreRead.sol/StoreRead.abi.json +++ b/packages/world/abi/StoreRead.sol/StoreRead.abi.json @@ -307,12 +307,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" } ] \ No newline at end of file diff --git a/packages/world/abi/StoreSwitch.sol/StoreSwitch.abi.json b/packages/world/abi/StoreSwitch.sol/StoreSwitch.abi.json index 613386a47c..0637a088a0 100644 --- a/packages/world/abi/StoreSwitch.sol/StoreSwitch.abi.json +++ b/packages/world/abi/StoreSwitch.sol/StoreSwitch.abi.json @@ -1,7 +1 @@ -[ - { - "inputs": [], - "name": "StoreSwitch_InvalidInsideConstructor", - "type": "error" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/packages/world/abi/World.sol/World.abi.json b/packages/world/abi/World.sol/World.abi.json index e30e1bae77..013356766d 100644 --- a/packages/world/abi/World.sol/World.abi.json +++ b/packages/world/abi/World.sol/World.abi.json @@ -590,13 +590,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/src/IStore.sol/IStore.abi.json b/packages/world/abi/src/IStore.sol/IStore.abi.json index fa4b3d868f..9af8379342 100644 --- a/packages/world/abi/src/IStore.sol/IStore.abi.json +++ b/packages/world/abi/src/IStore.sol/IStore.abi.json @@ -428,13 +428,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/src/IStore.sol/IStoreData.abi.json b/packages/world/abi/src/IStore.sol/IStoreData.abi.json index 44c5939df8..3d3e52c5c6 100644 --- a/packages/world/abi/src/IStore.sol/IStoreData.abi.json +++ b/packages/world/abi/src/IStore.sol/IStoreData.abi.json @@ -290,13 +290,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/packages/world/abi/src/IStore.sol/IStoreRead.abi.json b/packages/world/abi/src/IStore.sol/IStoreRead.abi.json index 182dfafa10..e6955707bb 100644 --- a/packages/world/abi/src/IStore.sol/IStoreRead.abi.json +++ b/packages/world/abi/src/IStore.sol/IStoreRead.abi.json @@ -196,12 +196,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "isStore", - "outputs": [], - "stateMutability": "view", - "type": "function" } ] \ No newline at end of file diff --git a/packages/world/abi/src/StoreSwitch.sol/StoreSwitch.abi.json b/packages/world/abi/src/StoreSwitch.sol/StoreSwitch.abi.json index 613386a47c..0637a088a0 100644 --- a/packages/world/abi/src/StoreSwitch.sol/StoreSwitch.abi.json +++ b/packages/world/abi/src/StoreSwitch.sol/StoreSwitch.abi.json @@ -1,7 +1 @@ -[ - { - "inputs": [], - "name": "StoreSwitch_InvalidInsideConstructor", - "type": "error" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/packages/world/gas-report.json b/packages/world/gas-report.json index 8a8503e015..36f0745446 100644 --- a/packages/world/gas-report.json +++ b/packages/world/gas-report.json @@ -3,67 +3,67 @@ "file": "test/KeysInTableModule.t.sol", "test": "testInstallComposite", "name": "install keys in table module", - "gasUsed": 1278450 + "gasUsed": 1255918 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "install keys in table module", - "gasUsed": 1278450 + "gasUsed": 1255918 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallGas", "name": "set a record on a table with keysInTableModule installed", - "gasUsed": 192836 + "gasUsed": 189004 }, { "file": "test/KeysInTableModule.t.sol", "test": "testInstallSingleton", "name": "install keys in table module", - "gasUsed": 1278450 + "gasUsed": 1255918 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "install keys in table module", - "gasUsed": 1278450 + "gasUsed": 1255918 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "change a composite record on a table with keysInTableModule installed", - "gasUsed": 29884 + "gasUsed": 28442 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookCompositeGas", "name": "delete a composite record on a table with keysInTableModule installed", - "gasUsed": 288706 + "gasUsed": 270004 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "install keys in table module", - "gasUsed": 1278450 + "gasUsed": 1255918 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "change a record on a table with keysInTableModule installed", - "gasUsed": 28498 + "gasUsed": 27056 }, { "file": "test/KeysInTableModule.t.sol", "test": "testSetAndDeleteRecordHookGas", "name": "delete a record on a table with keysInTableModule installed", - "gasUsed": 151360 + "gasUsed": 141266 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testGetKeysWithValueGas", "name": "install keys with value module", - "gasUsed": 623327 + "gasUsed": 606592 }, { "file": "test/KeysWithValueModule.t.sol", @@ -81,79 +81,79 @@ "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "install keys with value module", - "gasUsed": 623327 + "gasUsed": 606592 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testInstall", "name": "set a record on a table with KeysWithValueModule installed", - "gasUsed": 165159 + "gasUsed": 162059 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "install keys with value module", - "gasUsed": 623327 + "gasUsed": 606592 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "change a record on a table with KeysWithValueModule installed", - "gasUsed": 134812 + "gasUsed": 129713 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetAndDeleteRecordHook", "name": "delete a record on a table with KeysWithValueModule installed", - "gasUsed": 54015 + "gasUsed": 50859 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "install keys with value module", - "gasUsed": 623327 + "gasUsed": 606592 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "set a field on a table with KeysWithValueModule installed", - "gasUsed": 173182 + "gasUsed": 170334 }, { "file": "test/KeysWithValueModule.t.sol", "test": "testSetField", "name": "change a field on a table with KeysWithValueModule installed", - "gasUsed": 137540 + "gasUsed": 132692 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueNotQuery", "name": "CombinedHasHasValueNotQuery", - "gasUsed": 163229 + "gasUsed": 163160 }, { "file": "test/query.t.sol", "test": "testCombinedHasHasValueQuery", "name": "CombinedHasHasValueQuery", - "gasUsed": 77052 + "gasUsed": 77029 }, { "file": "test/query.t.sol", "test": "testCombinedHasNotQuery", "name": "CombinedHasNotQuery", - "gasUsed": 222589 + "gasUsed": 222474 }, { "file": "test/query.t.sol", "test": "testCombinedHasQuery", "name": "CombinedHasQuery", - "gasUsed": 139921 + "gasUsed": 139829 }, { "file": "test/query.t.sol", "test": "testCombinedHasValueNotQuery", "name": "CombinedHasValueNotQuery", - "gasUsed": 139124 + "gasUsed": 139055 }, { "file": "test/query.t.sol", @@ -165,19 +165,19 @@ "file": "test/query.t.sol", "test": "testHasQuery", "name": "HasQuery", - "gasUsed": 31582 + "gasUsed": 31559 }, { "file": "test/query.t.sol", "test": "testHasQuery1000Keys", "name": "HasQuery with 1000 keys", - "gasUsed": 10806630 + "gasUsed": 10806607 }, { "file": "test/query.t.sol", "test": "testHasQuery100Keys", "name": "HasQuery with 100 keys", - "gasUsed": 1009033 + "gasUsed": 1009010 }, { "file": "test/query.t.sol", @@ -189,132 +189,132 @@ "file": "test/query.t.sol", "test": "testNotValueQuery", "name": "NotValueQuery", - "gasUsed": 70232 + "gasUsed": 70209 }, { "file": "test/SnapSyncModule.t.sol", "test": "testSnapSyncGas", "name": "Call snap sync on a table with 1 record", - "gasUsed": 42620 + "gasUsed": 39819 }, { "file": "test/SnapSyncModule.t.sol", "test": "testSnapSyncGas", "name": "Call snap sync on a table with 2 records", - "gasUsed": 60802 + "gasUsed": 57129 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "install unique entity module", - "gasUsed": 818196 + "gasUsed": 797781 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstall", "name": "get a unique entity nonce (non-root module)", - "gasUsed": 72422 + "gasUsed": 71099 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "installRoot unique entity module", - "gasUsed": 789214 + "gasUsed": 769225 }, { "file": "test/UniqueEntityModule.t.sol", "test": "testInstallRoot", "name": "get a unique entity nonce (root module)", - "gasUsed": 72422 + "gasUsed": 71099 }, { "file": "test/World.t.sol", "test": "testDeleteRecord", "name": "Delete record", - "gasUsed": 16058 + "gasUsed": 15060 }, { "file": "test/World.t.sol", "test": "testPushToField", "name": "Push data to the table", - "gasUsed": 96417 + "gasUsed": 95419 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a fallback system", - "gasUsed": 85624 + "gasUsed": 81644 }, { "file": "test/World.t.sol", "test": "testRegisterFallbackSystem", "name": "Register a root fallback system", - "gasUsed": 76808 + "gasUsed": 72828 }, { "file": "test/World.t.sol", "test": "testRegisterFunctionSelector", "name": "Register a function selector", - "gasUsed": 106219 + "gasUsed": 102239 }, { "file": "test/World.t.sol", "test": "testRegisterNamespace", "name": "Register a new namespace", - "gasUsed": 160613 + "gasUsed": 157623 }, { "file": "test/World.t.sol", "test": "testRegisterRootFunctionSelector", "name": "Register a root function selector", - "gasUsed": 92711 + "gasUsed": 88731 }, { "file": "test/World.t.sol", "test": "testRegisterTable", "name": "Register a new table in the namespace", - "gasUsed": 260715 + "gasUsed": 255729 }, { "file": "test/World.t.sol", "test": "testSetField", "name": "Write data to a table field", - "gasUsed": 44625 + "gasUsed": 43649 }, { "file": "test/World.t.sol", "test": "testSetMetadata", "name": "Set metadata", - "gasUsed": 282542 + "gasUsed": 279560 }, { "file": "test/World.t.sol", "test": "testSetRecord", "name": "Write data to the table", - "gasUsed": 42502 + "gasUsed": 41504 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (cold)", - "gasUsed": 37949 + "gasUsed": 38906 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testPopFromField", "name": "pop 1 address (warm)", - "gasUsed": 22739 + "gasUsed": 21696 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (cold)", - "gasUsed": 40283 + "gasUsed": 41285 }, { "file": "test/WorldDynamicUpdate.t.sol", "test": "testUpdateInField", "name": "updateInField 1 item (warm)", - "gasUsed": 25485 + "gasUsed": 24487 } ] diff --git a/packages/world/src/Utils.sol b/packages/world/src/Utils.sol index 5446441c1b..dd5a4138dc 100644 --- a/packages/world/src/Utils.sol +++ b/packages/world/src/Utils.sol @@ -14,7 +14,7 @@ library Utils { * because they're delegatecalled and the name isn't passed in calldata */ function systemNamespace() internal view returns (bytes16) { - if (StoreSwitch.isDelegateCall()) { + if (StoreSwitch.getStoreAddress() == address(this)) { return ""; } else { bytes32 resourceSelector = SystemRegistry.get(address(this)); diff --git a/packages/world/src/WorldContext.sol b/packages/world/src/WorldContext.sol index bb511ffaed..1d5f6c00ab 100644 --- a/packages/world/src/WorldContext.sol +++ b/packages/world/src/WorldContext.sol @@ -16,6 +16,6 @@ abstract contract WorldContext { } function _world() internal view returns (address) { - return StoreSwitch.inferStoreAddress(); + return StoreSwitch.getStoreAddress(); } } diff --git a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol index bb012b6eb0..82d931d6ce 100644 --- a/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol +++ b/packages/world/src/modules/keyswithvalue/KeysWithValueHook.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0; import { IStoreHook } from "@latticexyz/store/src/IStore.sol"; import { Bytes } from "@latticexyz/store/src/Bytes.sol"; +import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { IBaseWorld } from "../../interfaces/IBaseWorld.sol"; import { ResourceSelector } from "../../ResourceSelector.sol"; @@ -24,11 +25,15 @@ contract KeysWithValueHook is IStoreHook { using ArrayLib for bytes32[]; using ResourceSelector for bytes32; + function _world() internal view returns (IBaseWorld) { + return IBaseWorld(StoreSwitch.getStoreAddress()); + } + function onSetRecord(bytes32 sourceTableId, bytes32[] memory key, bytes memory data) public { bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); // Get the previous value - bytes32 previousValue = keccak256(IBaseWorld(msg.sender).getRecord(sourceTableId, key)); + bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, key)); // Remove the key from the list of keys with the previous value _removeKeyFromList(targetTableId, key[0], previousValue); @@ -39,21 +44,21 @@ contract KeysWithValueHook is IStoreHook { function onBeforeSetField(bytes32 sourceTableId, bytes32[] memory key, uint8, bytes memory) public { // Remove the key from the list of keys with the previous value - bytes32 previousValue = keccak256(IBaseWorld(msg.sender).getRecord(sourceTableId, key)); + bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, key)); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); _removeKeyFromList(targetTableId, key[0], previousValue); } function onAfterSetField(bytes32 sourceTableId, bytes32[] memory key, uint8, bytes memory) public { // Add the key to the list of keys with the new value - bytes32 newValue = keccak256(IBaseWorld(msg.sender).getRecord(sourceTableId, key)); + bytes32 newValue = keccak256(_world().getRecord(sourceTableId, key)); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); KeysWithValue.push(targetTableId, newValue, key[0]); } function onDeleteRecord(bytes32 sourceTableId, bytes32[] memory key) public { // Remove the key from the list of keys with the previous value - bytes32 previousValue = keccak256(IBaseWorld(msg.sender).getRecord(sourceTableId, key)); + bytes32 previousValue = keccak256(_world().getRecord(sourceTableId, key)); bytes32 targetTableId = getTargetTableSelector(MODULE_NAMESPACE, sourceTableId); _removeKeyFromList(targetTableId, key[0], previousValue); } diff --git a/packages/world/src/modules/uniqueentity/getUniqueEntity.sol b/packages/world/src/modules/uniqueentity/getUniqueEntity.sol index 2482056b51..6142514a97 100644 --- a/packages/world/src/modules/uniqueentity/getUniqueEntity.sol +++ b/packages/world/src/modules/uniqueentity/getUniqueEntity.sol @@ -13,7 +13,7 @@ import { IUniqueEntitySystem } from "../../interfaces/IUniqueEntitySystem.sol"; * For usage outside of a World, use the overload that takes an explicit store argument. */ function getUniqueEntity() returns (bytes32 uniqueEntity) { - address world = StoreSwitch.inferStoreAddress(); + address world = StoreSwitch.getStoreAddress(); return IUniqueEntitySystem(world).uniqueEntity_system_getUniqueEntity(); } diff --git a/packages/world/test/World.t.sol b/packages/world/test/World.t.sol index 11d9ed1af1..d50f332e0a 100644 --- a/packages/world/test/World.t.sol +++ b/packages/world/test/World.t.sol @@ -44,6 +44,10 @@ contract WorldTestSystem is System { error WorldTestSystemError(string err); event WorldTestSystemLog(string log); + function getStoreAddress() public view returns (address) { + return StoreSwitch.getStoreAddress(); + } + function msgSender() public view returns (address) { return _msgSender(); } @@ -72,7 +76,7 @@ contract WorldTestSystem is System { function writeData(bytes16 namespace, bytes16 name, bool data) public { bytes32[] memory key = new bytes32[](0); - if (StoreSwitch.isDelegateCall()) { + if (StoreSwitch.getStoreAddress() == address(this)) { bytes32 tableId = ResourceSelector.from(namespace, name); StoreCore.setRecord(tableId, key, abi.encodePacked(data)); } else { @@ -81,7 +85,7 @@ contract WorldTestSystem is System { } function emitCallType() public { - if (StoreSwitch.isDelegateCall()) { + if (StoreSwitch.getStoreAddress() == address(this)) { emit WorldTestSystemLog("delegatecall"); } else { emit WorldTestSystemLog("call"); @@ -191,8 +195,17 @@ contract WorldTest is Test, GasReporter { assertTrue(ResourceAccess.get(world, ROOT_NAMESPACE, address(this))); } - function testIsStore() public view { - world.isStore(); + function testStoreAddress() public { + // Register a system and use it to get storeAddress + WorldTestSystem system = new WorldTestSystem(); + world.registerSystem("namespace", "testSystem", system, false); + bytes memory result = world.call( + "namespace", + "testSystem", + abi.encodeWithSelector(WorldTestSystem.getStoreAddress.selector) + ); + + assertEq(abi.decode(result, (address)), address(world)); } function testRegisterNamespace() public { diff --git a/templates/phaser/packages/contracts/package.json b/templates/phaser/packages/contracts/package.json index f172c06e2e..ff2d36d0d1 100644 --- a/templates/phaser/packages/contracts/package.json +++ b/templates/phaser/packages/contracts/package.json @@ -22,7 +22,6 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/cli": "link:../../../../packages/cli", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-contracts": "link:../../../../packages/std-contracts", "@latticexyz/store": "link:../../../../packages/store", "@latticexyz/world": "link:../../../../packages/world", "ethers": "^5.7.2" diff --git a/templates/phaser/packages/contracts/test/CounterTest.t.sol b/templates/phaser/packages/contracts/test/CounterTest.t.sol index ce4858a5c1..94c71dc8b4 100644 --- a/templates/phaser/packages/contracts/test/CounterTest.t.sol +++ b/templates/phaser/packages/contracts/test/CounterTest.t.sol @@ -2,13 +2,13 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; import { Counter, CounterTableId } from "../src/codegen/Tables.sol"; -contract CounterTest is MudV2Test { +contract CounterTest is MudTest { IWorld public world; function setUp() public override { diff --git a/templates/react/packages/contracts/package.json b/templates/react/packages/contracts/package.json index f172c06e2e..ff2d36d0d1 100644 --- a/templates/react/packages/contracts/package.json +++ b/templates/react/packages/contracts/package.json @@ -22,7 +22,6 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/cli": "link:../../../../packages/cli", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-contracts": "link:../../../../packages/std-contracts", "@latticexyz/store": "link:../../../../packages/store", "@latticexyz/world": "link:../../../../packages/world", "ethers": "^5.7.2" diff --git a/templates/react/packages/contracts/test/CounterTest.t.sol b/templates/react/packages/contracts/test/CounterTest.t.sol index ce4858a5c1..94c71dc8b4 100644 --- a/templates/react/packages/contracts/test/CounterTest.t.sol +++ b/templates/react/packages/contracts/test/CounterTest.t.sol @@ -2,13 +2,13 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; import { Counter, CounterTableId } from "../src/codegen/Tables.sol"; -contract CounterTest is MudV2Test { +contract CounterTest is MudTest { IWorld public world; function setUp() public override { diff --git a/templates/threejs/packages/contracts/package.json b/templates/threejs/packages/contracts/package.json index f172c06e2e..ff2d36d0d1 100644 --- a/templates/threejs/packages/contracts/package.json +++ b/templates/threejs/packages/contracts/package.json @@ -22,7 +22,6 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/cli": "link:../../../../packages/cli", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-contracts": "link:../../../../packages/std-contracts", "@latticexyz/store": "link:../../../../packages/store", "@latticexyz/world": "link:../../../../packages/world", "ethers": "^5.7.2" diff --git a/templates/vanilla/packages/contracts/package.json b/templates/vanilla/packages/contracts/package.json index f172c06e2e..ff2d36d0d1 100644 --- a/templates/vanilla/packages/contracts/package.json +++ b/templates/vanilla/packages/contracts/package.json @@ -22,7 +22,6 @@ "@ethersproject/providers": "^5.7.2", "@latticexyz/cli": "link:../../../../packages/cli", "@latticexyz/schema-type": "link:../../../../packages/schema-type", - "@latticexyz/std-contracts": "link:../../../../packages/std-contracts", "@latticexyz/store": "link:../../../../packages/store", "@latticexyz/world": "link:../../../../packages/world", "ethers": "^5.7.2" diff --git a/templates/vanilla/packages/contracts/test/CounterTest.t.sol b/templates/vanilla/packages/contracts/test/CounterTest.t.sol index ce4858a5c1..94c71dc8b4 100644 --- a/templates/vanilla/packages/contracts/test/CounterTest.t.sol +++ b/templates/vanilla/packages/contracts/test/CounterTest.t.sol @@ -2,13 +2,13 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { MudV2Test } from "@latticexyz/std-contracts/src/test/MudV2Test.t.sol"; +import { MudTest } from "@latticexyz/store/src/MudTest.sol"; import { getKeysWithValue } from "@latticexyz/world/src/modules/keyswithvalue/getKeysWithValue.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; import { Counter, CounterTableId } from "../src/codegen/Tables.sol"; -contract CounterTest is MudV2Test { +contract CounterTest is MudTest { IWorld public world; function setUp() public override {