diff --git a/.changeset/nine-countries-nail.md b/.changeset/nine-countries-nail.md new file mode 100644 index 0000000000..ab3ebf579b --- /dev/null +++ b/.changeset/nine-countries-nail.md @@ -0,0 +1,7 @@ +--- +"@latticexyz/store-consumer": patch +"@latticexyz/world-module-erc20": patch +--- + +Extracted StoreConsumer base contracts into an independent package. +Added a `registerNamespace` boolean to `WithWorld` to provide more control over namespace registration. diff --git a/packages/store-consumer/.gitignore b/packages/store-consumer/.gitignore new file mode 100644 index 0000000000..1e4ded714a --- /dev/null +++ b/packages/store-consumer/.gitignore @@ -0,0 +1,2 @@ +cache +out diff --git a/packages/store-consumer/CHANGELOG.md b/packages/store-consumer/CHANGELOG.md new file mode 100644 index 0000000000..2c56039cb9 --- /dev/null +++ b/packages/store-consumer/CHANGELOG.md @@ -0,0 +1 @@ +# @latticexyz/store-consumer diff --git a/packages/store-consumer/README.md b/packages/store-consumer/README.md new file mode 100644 index 0000000000..b1dd5652e1 --- /dev/null +++ b/packages/store-consumer/README.md @@ -0,0 +1,11 @@ +# Store Consumer Contracts + +> :warning: **Important note: these contracts have not been audited yet, so any production use is discouraged for now.** + +This set of contracts provides an easy way to make use of a `Store` for read and write operations, with the possibility of fully abstracting away the type of underlying store being used. + +- `StoreConsumer`: all contracts that don't explicitly need to know which type of store is being used can inherit from `StoreConsumer`, which abstracts the way in which `ResourceId`s are encoded. This allows us to have composable contracts whose implementations don't depend on the type of the underlying Store. +- `WithStore(address) is StoreConsumer`: this contract initializes the store, using the contract's internal storage or the provided external `Store`. It encodes `ResourceId`s using `ResourceIdLib` from the `@latticexyz/store` package. +- `WithWorld(IBaseWorld, bytes14) is WithStore`: initializes the store and also registers the provided namespace in the provided World. It encodes `ResourceId`s using `WorldResourceIdLib` (using the namespace). It also provides an `onlyNamespace` modifier, which can be used to restrict access to certain functions, only allowing calls from addresses that have access to the namespace. + +For examples of how these are used in practice you can check the [examples directory](./src/examples/) or our [ERC20 World Module](../world-module-erc20/). diff --git a/packages/store-consumer/foundry.toml b/packages/store-consumer/foundry.toml new file mode 100644 index 0000000000..f0e017f5a0 --- /dev/null +++ b/packages/store-consumer/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +solc = "0.8.24" +ffi = false +fuzz_runs = 256 +optimizer = true +optimizer_runs = 3000 +verbosity = 2 +allow_paths = ["../../node_modules", "../"] +src = "src" +out = "out" +bytecode_hash = "none" +extra_output_files = [ + "abi", + "evm.bytecode" +] diff --git a/packages/store-consumer/gas-report.json b/packages/store-consumer/gas-report.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/store-consumer/gas-report.json @@ -0,0 +1 @@ +[] diff --git a/packages/store-consumer/package.json b/packages/store-consumer/package.json new file mode 100644 index 0000000000..adf49b27eb --- /dev/null +++ b/packages/store-consumer/package.json @@ -0,0 +1,53 @@ +{ + "name": "@latticexyz/store-consumer", + "version": "2.2.14", + "description": "Store Consumer Contracts", + "repository": { + "type": "git", + "url": "https://github.com/latticexyz/mud.git", + "directory": "packages/store-consumer" + }, + "license": "MIT", + "type": "module", + "exports": { + "./out/*": "./out/*" + }, + "typesVersions": { + "*": {} + }, + "files": [ + "out", + "src" + ], + "scripts": { + "build": "pnpm run build:abi && pnpm run build:abi-ts", + "build:abi": "forge build", + "build:abi-ts": "abi-ts", + "clean": "pnpm run clean:abi && pnpm run clean:js", + "clean:abi": "forge clean", + "clean:js": "shx rm -rf dist", + "clean:mud": "shx rm -rf src/**/codegen", + "dev": "tsup --watch", + "gas-report": "gas-report --save gas-report.json", + "lint": "solhint --config ./.solhint.json 'src/**/*.sol'", + "test": "forge test", + "test:ci": "pnpm run test" + }, + "dependencies": { + "@latticexyz/schema-type": "workspace:*", + "@latticexyz/store": "workspace:*", + "@latticexyz/world": "workspace:*" + }, + "devDependencies": { + "@latticexyz/abi-ts": "workspace:*", + "@latticexyz/gas-report": "workspace:*", + "@types/node": "^18.15.11", + "forge-std": "https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262", + "solhint": "^3.3.7", + "tsup": "^6.7.0", + "vitest": "0.34.6" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/store-consumer/remappings.txt b/packages/store-consumer/remappings.txt new file mode 100644 index 0000000000..e2614d0aa8 --- /dev/null +++ b/packages/store-consumer/remappings.txt @@ -0,0 +1,2 @@ +forge-std/=node_modules/forge-std/src/ +@latticexyz/=node_modules/@latticexyz/ diff --git a/packages/store-consumer/src/examples/SimpleVault.sol b/packages/store-consumer/src/examples/SimpleVault.sol new file mode 100644 index 0000000000..edf16e6dd6 --- /dev/null +++ b/packages/store-consumer/src/examples/SimpleVault.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; +import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; + +import { WithWorld } from "../experimental/WithWorld.sol"; + +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} + +/** + * @title SimpleVault (NOT AUDITED) + * @dev Simple example of a Vault that allows accounts with namespace access to transfer its tokens out + * IMPORTANT: this contract expects an existing namespace + */ +contract SimpleVault is WithWorld { + error SimpleVault_TransferFailed(); + + constructor(IBaseWorld world, bytes14 namespace) WithWorld(world, namespace, false) {} + + // Only accounts with namespace access (e.g. namespace systems) can transfer the ERC20 tokens held by this contract + function transferTo(IERC20 token, address to, uint256 amount) external onlyNamespace { + require(token.transfer(to, amount), "Transfer failed"); + } + + // ... other methods to deposit, etc +} diff --git a/packages/world-module-erc20/src/Context.sol b/packages/store-consumer/src/experimental/Context.sol similarity index 100% rename from packages/world-module-erc20/src/Context.sol rename to packages/store-consumer/src/experimental/Context.sol diff --git a/packages/world-module-erc20/src/StoreConsumer.sol b/packages/store-consumer/src/experimental/StoreConsumer.sol similarity index 100% rename from packages/world-module-erc20/src/StoreConsumer.sol rename to packages/store-consumer/src/experimental/StoreConsumer.sol diff --git a/packages/world-module-erc20/src/WithStore.sol b/packages/store-consumer/src/experimental/WithStore.sol similarity index 100% rename from packages/world-module-erc20/src/WithStore.sol rename to packages/store-consumer/src/experimental/WithStore.sol diff --git a/packages/world-module-erc20/src/WithWorld.sol b/packages/store-consumer/src/experimental/WithWorld.sol similarity index 57% rename from packages/world-module-erc20/src/WithWorld.sol rename to packages/store-consumer/src/experimental/WithWorld.sol index 77295db24f..6202e273c7 100644 --- a/packages/world-module-erc20/src/WithWorld.sol +++ b/packages/store-consumer/src/experimental/WithWorld.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.24; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; @@ -11,6 +12,9 @@ import { WithStore } from "./WithStore.sol"; abstract contract WithWorld is WithStore { bytes14 public immutable namespace; + error WithWorld_RootNamespaceNotAllowed(); + error WithWorld_NamespaceAlreadyExists(); + error WithWorld_NamespaceDoesNotExists(); error WithWorld_CallerHasNoNamespaceAccess(); modifier onlyNamespace() { @@ -21,19 +25,36 @@ abstract contract WithWorld is WithStore { _; } - constructor(IBaseWorld world, bytes14 _namespace) WithStore(address(world)) { + constructor(IBaseWorld _world, bytes14 _namespace, bool registerNamespace) WithStore(address(_world)) { + if (_namespace == bytes14(0)) { + revert WithWorld_RootNamespaceNotAllowed(); + } + namespace = _namespace; ResourceId namespaceId = getNamespaceId(); - // This will revert if namespace already exists - world.registerNamespace(namespaceId); + bool namespaceExists = ResourceIds.getExists(namespaceId); + + if (registerNamespace) { + if (namespaceExists) { + revert WithWorld_NamespaceAlreadyExists(); + } + + _world.registerNamespace(namespaceId); + } else if (!namespaceExists) { + revert WithWorld_NamespaceDoesNotExists(); + } } function getNamespaceId() public view returns (ResourceId) { return WorldResourceIdLib.encodeNamespace(namespace); } + function getWorld() public view returns (IBaseWorld) { + return IBaseWorld(getStore()); + } + function _encodeResourceId(bytes2 typeId, bytes16 name) internal view virtual override returns (ResourceId) { return WorldResourceIdLib.encode(typeId, namespace, name); } diff --git a/packages/world-module-erc20/test/StoreConsumer.t.sol b/packages/store-consumer/test/StoreConsumer.t.sol similarity index 76% rename from packages/world-module-erc20/test/StoreConsumer.t.sol rename to packages/store-consumer/test/StoreConsumer.t.sol index 505698949f..65926f8849 100644 --- a/packages/world-module-erc20/test/StoreConsumer.t.sol +++ b/packages/store-consumer/test/StoreConsumer.t.sol @@ -16,9 +16,9 @@ import { Tables, ResourceIds } from "@latticexyz/store/src/codegen/index.sol"; import { StoreCore } from "@latticexyz/store/src/Store.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { StoreConsumer } from "../src/StoreConsumer.sol"; -import { WithStore } from "../src/WithStore.sol"; -import { WithWorld } from "../src/WithWorld.sol"; +import { StoreConsumer } from "../src/experimental/StoreConsumer.sol"; +import { WithStore } from "../src/experimental/WithStore.sol"; +import { WithWorld } from "../src/experimental/WithWorld.sol"; abstract contract MockStoreConsumer is StoreConsumer { function getStoreAddress() public view virtual returns (address) { @@ -37,14 +37,21 @@ contract MockWithStore is WithStore, MockStoreConsumer { contract MockWithInternalStore is MockWithStore(address(this)) {} contract MockWithWorld is WithWorld, MockStoreConsumer { - constructor(IBaseWorld world, bytes14 namespace) WithWorld(world, namespace) { - ResourceId namespaceId = getNamespaceId(); - world.grantAccess(namespaceId, address(this)); + constructor( + IBaseWorld world, + bytes14 namespace, + bool registerNamespace + ) WithWorld(world, namespace, registerNamespace) {} + + function grantNamespaceAccess(address to) external { + getWorld().grantAccess(getNamespaceId(), to); + } - // Transfer ownership to the creator so we can test `onlyNamespace` - world.transferOwnership(namespaceId, _msgSender()); + function transferNamespaceOwnership(address to) external { + getWorld().transferOwnership(getNamespaceId(), to); } - function onlyCallableByNamespace() public view onlyNamespace {} + + function onlyCallableByNamespace() external view onlyNamespace {} } contract StoreConsumerTest is Test, GasReporter { @@ -61,7 +68,7 @@ contract StoreConsumerTest is Test, GasReporter { function testWithWorld() public { IBaseWorld world = createWorld(); bytes14 namespace = "myNamespace"; - MockWithWorld mock = new MockWithWorld(world, namespace); + MockWithWorld mock = new MockWithWorld(world, namespace, true); assertEq(mock.getStoreAddress(), address(world)); StoreSwitch.setStoreAddress(address(world)); @@ -72,10 +79,12 @@ contract StoreConsumerTest is Test, GasReporter { function testOnlyNamespace() public { IBaseWorld world = createWorld(); + StoreSwitch.setStoreAddress(address(world)); + bytes14 namespace = "myNamespace"; ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); - MockWithWorld mock = new MockWithWorld(world, namespace); - StoreSwitch.setStoreAddress(address(world)); + MockWithWorld mock = new MockWithWorld(world, namespace, true); + mock.transferNamespaceOwnership(address(this)); address alice = address(0x1234); diff --git a/packages/store-consumer/tsconfig.json b/packages/store-consumer/tsconfig.json new file mode 100644 index 0000000000..9b0bf57752 --- /dev/null +++ b/packages/store-consumer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["mud.config.ts", "ts"] +} diff --git a/packages/world-module-erc20/README.md b/packages/world-module-erc20/README.md index e5234edf75..8545c355e2 100644 --- a/packages/world-module-erc20/README.md +++ b/packages/world-module-erc20/README.md @@ -4,15 +4,11 @@ ## ERC20 contracts -In order to achieve a similar level of composability to [`OpenZeppelin` ERC20 contract extensions](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20), we provide a way to abstract the underlying Store being used. This allows developers to easily create ERC20 tokens that can either use its own storage as the Store, or attach themselves to an existing World. +In order to achieve a similar level of composability to [`OpenZeppelin` ERC20 contract extensions](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20), we use our experimental [`StoreConsumer` contract](../store-consumer) to abstract the underlying Store being used. This allows developers to easily create ERC20 tokens that can either use its own storage as the Store, or attach themselves to an existing World. -- `StoreConsumer`: all contracts inherit from `StoreConsumer`, which abstracts the way in which `ResourceId`s are encoded. This allows us to have composable contracts whose implementations don't depend on the type of Store being used. -- `WithStore(address) is StoreConsumer`: this contract initializes the store, using the contract's internal storage or the provided external `Store`. It encodes `ResourceId`s using `ResourceIdLib` from the `@latticexyz/store` package. -- `WithWorld(IBaseWorld, bytes14) is WithStore`: initializes the store and also registers the provided namespace in the provided World. It encodes `ResourceId`s using `WorldResourceIdLib` (using the namespace). It also provides an `onlyNamespace` modifier, which can be used to restrict access to certain functions, only allowing calls from addresses that have access to the namespace. +The `MUDERC20` contract is the base ERC20 implementation adapted from Openzeppelin's ERC20. Contains the ERC20 logic, reads/writes to the store through MUD's codegen libraries and initializes the tables it needs. As these libraries use `StoreSwitch` internally, this contract doesn't need to know about the store it's interacting with (it can be internal storage, an external `Store` or a `World`). -- `MUDERC20`: base ERC20 implementation adapted from Openzeppelin's ERC20. Contains the ERC20 logic, reads/writes to the store through MUD's codegen libraries and initializes the tables it needs. As these libraries use `StoreSwitch` internally, this contract doesn't need to know about the store it's interacting with (it can be internal storage, an external `Store` or a `World`). - -- Extensions and other contracts: contracts like `Ownable`, `Pausable`, `ERC20Burnable`, etc are adapted from `OpenZeppelin` contracts to use MUD's codegen libraries to read and write from a `Store`. They inherit from `StoreConsumer`, so they can obtain the `ResourceId` for the tables they use using `_encodeResourceId()`. +Extensions and other contracts: contracts like `Ownable`, `Pausable`, `ERC20Burnable`, etc are adapted from `OpenZeppelin` contracts to use MUD's codegen libraries to read and write from a `Store`. They inherit from `StoreConsumer`, so they can obtain the `ResourceId` for the tables they use using `_encodeResourceId()`. ### Example 1: Using the contract's storage @@ -53,7 +49,7 @@ contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { bytes14 namespace, string memory name, string memory symbol - ) WithWorld(world, namespace) MUDERC20(name, symbol) { + ) WithWorld(world, namespace, true) MUDERC20(name, symbol) { // transfer namespace ownership to the creator world.transferOwnership(getNamespaceId(), _msgSender()); } diff --git a/packages/world-module-erc20/gas-report.json b/packages/world-module-erc20/gas-report.json index f0fa3ef9e5..4c5fdc800d 100644 --- a/packages/world-module-erc20/gas-report.json +++ b/packages/world-module-erc20/gas-report.json @@ -51,7 +51,7 @@ "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", "test": "testTransfer", "name": "world_transfer", - "gasUsed": 82184 + "gasUsed": 82206 }, { "file": "test/ERC20BaseTest.sol:ERC20WithWorldTest", @@ -105,7 +105,7 @@ "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testBurn", "name": "world_burn", - "gasUsed": 70689 + "gasUsed": 70734 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", @@ -123,7 +123,7 @@ "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", "test": "testTransfer", "name": "world_transfer", - "gasUsed": 82206 + "gasUsed": 82162 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableWithWorldTest", @@ -135,7 +135,7 @@ "file": "test/ERC20Module.t.sol:ERC20ModuleTest", "test": "testInstall", "name": "install erc20 module", - "gasUsed": 4487618 + "gasUsed": 4499347 }, { "file": "test/ERC20Pausable.t.sol:ERC20PausableWithInternalStoreTest", @@ -195,19 +195,19 @@ "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testMint", "name": "world_mint", - "gasUsed": 109674 + "gasUsed": 109697 }, { "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testPause", "name": "world_pause", - "gasUsed": 66034 + "gasUsed": 66012 }, { "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", "test": "testPause", "name": "world_unpause", - "gasUsed": 44119 + "gasUsed": 44097 }, { "file": "test/ERC20Pausable.t.sol:ERC20PausableWithWorldTest", diff --git a/packages/world-module-erc20/package.json b/packages/world-module-erc20/package.json index e5a483c838..47b74eb7fd 100644 --- a/packages/world-module-erc20/package.json +++ b/packages/world-module-erc20/package.json @@ -48,6 +48,7 @@ "dependencies": { "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", + "@latticexyz/store-consumer": "workspace:*", "@latticexyz/world": "workspace:*" }, "devDependencies": { diff --git a/packages/world-module-erc20/remappings.txt b/packages/world-module-erc20/remappings.txt index 66be45ecbd..c4d992480e 100644 --- a/packages/world-module-erc20/remappings.txt +++ b/packages/world-module-erc20/remappings.txt @@ -1,3 +1,3 @@ ds-test/=node_modules/ds-test/src/ forge-std/=node_modules/forge-std/src/ -@latticexyz/=node_modules/@latticexyz/ \ No newline at end of file +@latticexyz/=node_modules/@latticexyz/ diff --git a/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol b/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol index 4b1752c729..ae3410aec3 100644 --- a/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol +++ b/packages/world-module-erc20/src/examples/ERC20WithInternalStore.sol @@ -3,11 +3,11 @@ pragma solidity >=0.8.24; // Adapted example from OpenZeppelin's Contract Wizard: https://wizard.openzeppelin.com/ -import { WithStore } from "../WithStore.sol"; -import { Ownable } from "../Ownable.sol"; -import { ERC20Pausable } from "../ERC20Pausable.sol"; -import { ERC20Burnable } from "../ERC20Burnable.sol"; -import { MUDERC20 } from "../MUDERC20.sol"; +import { WithStore } from "@latticexyz/store-consumer/src/experimental/WithStore.sol"; +import { Ownable } from "../experimental/Ownable.sol"; +import { ERC20Pausable } from "../experimental/ERC20Pausable.sol"; +import { ERC20Burnable } from "../experimental/ERC20Burnable.sol"; +import { MUDERC20 } from "../experimental/MUDERC20.sol"; contract ERC20WithInternalStore is WithStore(address(this)), MUDERC20, ERC20Pausable, ERC20Burnable, Ownable { constructor() MUDERC20("MyERC20", "MTK") Ownable(_msgSender()) {} diff --git a/packages/world-module-erc20/src/examples/ERC20WithWorld.sol b/packages/world-module-erc20/src/examples/ERC20WithWorld.sol index 809efa3b2a..7023600048 100644 --- a/packages/world-module-erc20/src/examples/ERC20WithWorld.sol +++ b/packages/world-module-erc20/src/examples/ERC20WithWorld.sol @@ -3,11 +3,13 @@ pragma solidity >=0.8.24; // Adapted example from OpenZeppelin's Contract Wizard: https://wizard.openzeppelin.com/ +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; -import { WithWorld } from "../WithWorld.sol"; -import { ERC20Pausable } from "../ERC20Pausable.sol"; -import { ERC20Burnable } from "../ERC20Burnable.sol"; -import { MUDERC20 } from "../MUDERC20.sol"; +import { WithWorld } from "@latticexyz/store-consumer/src/experimental/WithWorld.sol"; + +import { ERC20Pausable } from "../experimental/ERC20Pausable.sol"; +import { ERC20Burnable } from "../experimental/ERC20Burnable.sol"; +import { MUDERC20 } from "../experimental/MUDERC20.sol"; contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { constructor( @@ -15,7 +17,7 @@ contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { bytes14 namespace, string memory name, string memory symbol - ) WithWorld(world, namespace) MUDERC20(name, symbol) { + ) WithWorld(world, namespace, true) MUDERC20(name, symbol) { // Transfer namespace ownership to the creator world.transferOwnership(getNamespaceId(), _msgSender()); } diff --git a/packages/world-module-erc20/src/Constants.sol b/packages/world-module-erc20/src/experimental/Constants.sol similarity index 100% rename from packages/world-module-erc20/src/Constants.sol rename to packages/world-module-erc20/src/experimental/Constants.sol diff --git a/packages/world-module-erc20/src/ERC20Burnable.sol b/packages/world-module-erc20/src/experimental/ERC20Burnable.sol similarity index 100% rename from packages/world-module-erc20/src/ERC20Burnable.sol rename to packages/world-module-erc20/src/experimental/ERC20Burnable.sol diff --git a/packages/world-module-erc20/src/ERC20Module.sol b/packages/world-module-erc20/src/experimental/ERC20Module.sol similarity index 94% rename from packages/world-module-erc20/src/ERC20Module.sol rename to packages/world-module-erc20/src/experimental/ERC20Module.sol index cab1a5b91d..7c2a371c92 100644 --- a/packages/world-module-erc20/src/ERC20Module.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Module.sol @@ -9,8 +9,8 @@ import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; -import { ERC20Registry } from "./codegen/tables/ERC20Registry.sol"; -import { ERC20WithWorld } from "./examples/ERC20WithWorld.sol"; +import { ERC20Registry } from "../codegen/tables/ERC20Registry.sol"; +import { ERC20WithWorld } from "../examples/ERC20WithWorld.sol"; import { ModuleConstants } from "./Constants.sol"; contract ERC20Module is Module { diff --git a/packages/world-module-erc20/src/ERC20Pausable.sol b/packages/world-module-erc20/src/experimental/ERC20Pausable.sol similarity index 100% rename from packages/world-module-erc20/src/ERC20Pausable.sol rename to packages/world-module-erc20/src/experimental/ERC20Pausable.sol diff --git a/packages/world-module-erc20/src/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol similarity index 94% rename from packages/world-module-erc20/src/MUDERC20.sol rename to packages/world-module-erc20/src/experimental/MUDERC20.sol index 97b64877f9..a7ec54069a 100644 --- a/packages/world-module-erc20/src/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -3,18 +3,17 @@ pragma solidity >=0.8.24; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { StoreConsumer } from "@latticexyz/store-consumer/src/experimental/StoreConsumer.sol"; +import { Context } from "@latticexyz/store-consumer/src/experimental/Context.sol"; -import { ERC20Metadata, ERC20MetadataData } from "./codegen/tables/ERC20Metadata.sol"; -import { TotalSupply } from "./codegen/tables/TotalSupply.sol"; -import { Balances } from "./codegen/tables/Balances.sol"; -import { Allowances } from "./codegen/tables/Allowances.sol"; +import { ERC20Metadata, ERC20MetadataData } from "../codegen/tables/ERC20Metadata.sol"; +import { TotalSupply } from "../codegen/tables/TotalSupply.sol"; +import { Balances } from "../codegen/tables/Balances.sol"; +import { Allowances } from "../codegen/tables/Allowances.sol"; -import { IERC20 } from "./interfaces/IERC20.sol"; -import { IERC20Metadata } from "./interfaces/IERC20Metadata.sol"; -import { IERC20Errors } from "./interfaces/IERC20Errors.sol"; - -import { Context } from "./Context.sol"; -import { StoreConsumer } from "./StoreConsumer.sol"; +import { IERC20 } from "../interfaces/IERC20.sol"; +import { IERC20Metadata } from "../interfaces/IERC20Metadata.sol"; +import { IERC20Errors } from "../interfaces/IERC20Errors.sol"; import { ERC20TableNames } from "./Constants.sol"; diff --git a/packages/world-module-erc20/src/Ownable.sol b/packages/world-module-erc20/src/experimental/Ownable.sol similarity index 93% rename from packages/world-module-erc20/src/Ownable.sol rename to packages/world-module-erc20/src/experimental/Ownable.sol index 9d5870e69b..c712c608c6 100644 --- a/packages/world-module-erc20/src/Ownable.sol +++ b/packages/world-module-erc20/src/experimental/Ownable.sol @@ -3,10 +3,10 @@ pragma solidity >=0.8.24; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { StoreConsumer } from "@latticexyz/store-consumer/src/experimental/StoreConsumer.sol"; +import { Context } from "@latticexyz/store-consumer/src/experimental/Context.sol"; -import { Owner } from "./codegen/tables/Owner.sol"; -import { StoreConsumer } from "./StoreConsumer.sol"; -import { Context } from "./Context.sol"; +import { Owner } from "../codegen/tables/Owner.sol"; import { OwnableTableNames } from "./Constants.sol"; /** diff --git a/packages/world-module-erc20/src/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol similarity index 92% rename from packages/world-module-erc20/src/Pausable.sol rename to packages/world-module-erc20/src/experimental/Pausable.sol index ca3beba56a..c4aae81051 100644 --- a/packages/world-module-erc20/src/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -3,10 +3,10 @@ pragma solidity >=0.8.24; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { StoreConsumer } from "@latticexyz/store-consumer/src/experimental/StoreConsumer.sol"; +import { Context } from "@latticexyz/store-consumer/src/experimental/Context.sol"; -import { Paused as PausedTable } from "./codegen/tables/Paused.sol"; -import { StoreConsumer } from "./StoreConsumer.sol"; -import { Context } from "./Context.sol"; +import { Paused as PausedTable } from "../codegen/tables/Paused.sol"; import { PausableTableNames } from "./Constants.sol"; /** diff --git a/packages/world-module-erc20/test/ERC20BaseTest.sol b/packages/world-module-erc20/test/ERC20BaseTest.sol index b6e680a6ea..17246b4c9e 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.sol @@ -12,14 +12,15 @@ import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; +import { WithStore } from "@latticexyz/store-consumer/src/experimental/WithStore.sol"; +import { WithWorld } from "@latticexyz/store-consumer/src/experimental/WithWorld.sol"; + import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; import { IERC20 } from "../src/interfaces/IERC20.sol"; import { IERC20Metadata } from "../src/interfaces/IERC20Metadata.sol"; import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; -import { WithStore } from "../src/WithStore.sol"; -import { WithWorld } from "../src/WithWorld.sol"; -import { MUDERC20 } from "../src/MUDERC20.sol"; +import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; library TestConstants { bytes14 constant ERC20_NAMESPACE = "mockerc20ns"; @@ -41,7 +42,7 @@ abstract contract MockERC20Base is MUDERC20 { contract MockERC20WithInternalStore is WithStore(address(this)), MockERC20Base {} contract MockERC20WithWorld is WithWorld, MockERC20Base { - constructor() WithWorld(createWorld(), TestConstants.ERC20_NAMESPACE) {} + constructor() WithWorld(createWorld(), TestConstants.ERC20_NAMESPACE, true) {} } abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Errors { diff --git a/packages/world-module-erc20/test/ERC20Burnable.t.sol b/packages/world-module-erc20/test/ERC20Burnable.t.sol index 027791a598..ee6ff7bbc5 100644 --- a/packages/world-module-erc20/test/ERC20Burnable.t.sol +++ b/packages/world-module-erc20/test/ERC20Burnable.t.sol @@ -13,8 +13,8 @@ import { createWorld } from "@latticexyz/world/test/createWorld.sol"; import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; -import { MUDERC20 } from "../src/MUDERC20.sol"; -import { ERC20Burnable } from "../src/ERC20Burnable.sol"; +import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; +import { ERC20Burnable } from "../src/experimental/ERC20Burnable.sol"; import { MockERC20Base, MockERC20WithInternalStore, MockERC20WithWorld, ERC20BehaviorTest, ERC20WithInternalStoreBehaviorTest, ERC20WithWorldBehaviorTest } from "./ERC20BaseTest.sol"; contract MockERC20WithInternalStoreBurnable is MockERC20WithInternalStore, ERC20Burnable {} diff --git a/packages/world-module-erc20/test/ERC20Module.t.sol b/packages/world-module-erc20/test/ERC20Module.t.sol index 17d85683d8..a3e567d3e1 100644 --- a/packages/world-module-erc20/test/ERC20Module.t.sol +++ b/packages/world-module-erc20/test/ERC20Module.t.sol @@ -17,8 +17,8 @@ import { IModuleErrors } from "@latticexyz/world/src/IModuleErrors.sol"; import { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; -import { ModuleConstants } from "../src/Constants.sol"; -import { ERC20Module } from "../src/ERC20Module.sol"; +import { ModuleConstants } from "../src/experimental/Constants.sol"; +import { ERC20Module } from "../src/experimental/ERC20Module.sol"; import { ERC20Registry } from "../src/codegen/tables/ERC20Registry.sol"; library TestConstants { diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol index 0d0b4feee8..a4b0dbdad3 100644 --- a/packages/world-module-erc20/test/ERC20Pausable.t.sol +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -10,8 +10,8 @@ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld. import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; -import { MUDERC20 } from "../src/MUDERC20.sol"; -import { Pausable, ERC20Pausable } from "../src/ERC20Pausable.sol"; +import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; +import { Pausable, ERC20Pausable } from "../src/experimental/ERC20Pausable.sol"; import { MockERC20Base, MockERC20WithInternalStore, MockERC20WithWorld, ERC20BehaviorTest, ERC20WithInternalStoreBehaviorTest, ERC20WithWorldBehaviorTest } from "./ERC20BaseTest.sol"; // Mock to include mint and burn functions diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd7bc8d0f7..32725b14b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -998,6 +998,40 @@ importers: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) + packages/store-consumer: + dependencies: + '@latticexyz/schema-type': + specifier: workspace:* + version: link:../schema-type + '@latticexyz/store': + specifier: workspace:* + version: link:../store + '@latticexyz/world': + specifier: workspace:* + version: link:../world + devDependencies: + '@latticexyz/abi-ts': + specifier: workspace:* + version: link:../abi-ts + '@latticexyz/gas-report': + specifier: workspace:* + version: link:../gas-report + '@types/node': + specifier: ^18.15.11 + version: 18.19.50 + forge-std: + specifier: https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262 + version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262 + solhint: + specifier: ^3.3.7 + version: 3.3.7 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.4.47)(typescript@5.4.2) + vitest: + specifier: 0.34.6 + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.33.0) + packages/store-indexer: dependencies: '@koa/cors': @@ -1313,6 +1347,9 @@ importers: '@latticexyz/store': specifier: workspace:* version: link:../store + '@latticexyz/store-consumer': + specifier: workspace:* + version: link:../store-consumer '@latticexyz/world': specifier: workspace:* version: link:../world @@ -7478,6 +7515,10 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262: + resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262} + version: 1.9.4 + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/74cfb77e308dd188d2f58864aaf44963ae6b88b1: resolution: {tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/74cfb77e308dd188d2f58864aaf44963ae6b88b1} version: 1.6.0 @@ -16376,7 +16417,7 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/aria-query@5.0.4': {} @@ -16411,7 +16452,7 @@ snapshots: '@types/better-sqlite3@7.6.4': dependencies: - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/body-parser@1.19.5': dependencies: @@ -16515,7 +16556,7 @@ snapshots: '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/koa__cors@4.0.3': dependencies: @@ -16595,7 +16636,7 @@ snapshots: '@types/sql.js@1.4.4': dependencies: '@types/emscripten': 1.39.6 - '@types/node': 18.15.11 + '@types/node': 18.19.50 '@types/stack-utils@2.0.1': {} @@ -19482,6 +19523,8 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1eea5bae12ae557d589f9f0f0edae2faa47cb262: {} + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/74cfb77e308dd188d2f58864aaf44963ae6b88b1: {} form-data@4.0.0: @@ -23849,7 +23892,7 @@ snapshots: debug: 4.3.7 mlly: 1.7.1 pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.0 vite: 4.5.5(@types/node@18.19.50)(terser@5.33.0) transitivePeerDependencies: - '@types/node' @@ -23926,7 +23969,7 @@ snapshots: local-pkg: 0.4.3 magic-string: 0.30.11 pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.0 std-env: 3.7.0 strip-literal: 1.3.0 tinybench: 2.9.0