From d11e625f6ddac0503c85f6961b40f6b0a1e3dba3 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 12 Feb 2025 14:07:54 -0300 Subject: [PATCH 01/10] Remove namespace from world consumer --- .../src/examples/SimpleVault.sol | 9 +- .../src/experimental/WorldConsumer.sol | 53 ++++---- .../world-consumer/test/WorldConsumer.t.sol | 113 +++++++++--------- 3 files changed, 86 insertions(+), 89 deletions(-) diff --git a/packages/world-consumer/src/examples/SimpleVault.sol b/packages/world-consumer/src/examples/SimpleVault.sol index 599431e8d4..db3805e27c 100644 --- a/packages/world-consumer/src/examples/SimpleVault.sol +++ b/packages/world-consumer/src/examples/SimpleVault.sol @@ -17,15 +17,18 @@ interface IERC20 { /** * @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 WorldConsumer { error SimpleVault_TransferFailed(); - constructor(IBaseWorld world, bytes14 namespace) WorldConsumer(world, namespace, false) {} + bytes14 immutable namespace; + + constructor(IBaseWorld world, bytes14 _namespace) WorldConsumer(world) { + namespace = _namespace; + } // 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 onlyWorld { + function transferTo(IERC20 token, address to, uint256 amount) external onlyNamespace(namespace) { require(token.transfer(to, amount), "Transfer failed"); } diff --git a/packages/world-consumer/src/experimental/WorldConsumer.sol b/packages/world-consumer/src/experimental/WorldConsumer.sol index c1bcfec177..5193ba5aae 100644 --- a/packages/world-consumer/src/experimental/WorldConsumer.sol +++ b/packages/world-consumer/src/experimental/WorldConsumer.sol @@ -6,65 +6,54 @@ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; 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 { NamespaceOwner } from "@latticexyz/world/src/codegen/tables/NamespaceOwner.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { WorldContextConsumer } from "@latticexyz/world/src/WorldContext.sol"; import { System } from "@latticexyz/world/src/System.sol"; abstract contract WorldConsumer is System { - bytes14 public immutable namespace; - ResourceId public immutable namespaceId; - error WorldConsumer_RootNamespaceNotAllowed(address worldAddress); - error WorldConsumer_NamespaceAlreadyExists(address worldAddress, bytes14 namespace); - error WorldConsumer_NamespaceDoesNotExists(address worldAddress, bytes14 namespace); error WorldConsumer_CallerHasNoNamespaceAccess(address worldAddress, bytes14 namespace, address caller); + error WorldConsumer_CallerIsNotNamespaceOwner(address worldAddress, bytes14 namespace, address caller); error WorldConsumer_CallerIsNotWorld(address worldAddress, address caller); error WorldConsumer_ValueNotAllowed(address worldAddress); modifier onlyWorld() { address world = _world(); - if (world != msg.sender) { - revert WorldConsumer_CallerIsNotWorld(world, msg.sender); - } + checkWorldIsCaller(world); _; } - modifier onlyNamespace() { + modifier onlyNamespace(bytes14 namespace) { address world = _world(); - if (world != msg.sender) { - revert WorldConsumer_CallerIsNotWorld(world, msg.sender); - } + checkWorldIsCaller(world); // We use WorldContextConsumer directly as we already know the world is the caller address sender = WorldContextConsumer._msgSender(); - if (!ResourceAccess.get(namespaceId, sender)) { + if (!ResourceAccess.get(WorldResourceIdLib.encodeNamespace(namespace), sender)) { revert WorldConsumer_CallerHasNoNamespaceAccess(world, namespace, sender); } _; } - constructor(IBaseWorld _world, bytes14 _namespace, bool registerNamespace) { - address worldAddress = address(_world); - StoreSwitch.setStoreAddress(worldAddress); + modifier onlyNamespaceOwner(bytes14 namespace) { + address world = _world(); + checkWorldIsCaller(world); - if (_namespace == bytes14(0)) { - revert WorldConsumer_RootNamespaceNotAllowed(worldAddress); + // We use WorldContextConsumer directly as we already know the world is the caller + address sender = WorldContextConsumer._msgSender(); + if (NamespaceOwner.get(WorldResourceIdLib.encodeNamespace(namespace)) != sender) { + revert WorldConsumer_CallerIsNotNamespaceOwner(world, namespace, sender); } - namespace = _namespace; - namespaceId = WorldResourceIdLib.encodeNamespace(_namespace); - bool namespaceExists = ResourceIds.getExists(namespaceId); + _; + } - if (registerNamespace) { - if (namespaceExists) { - revert WorldConsumer_NamespaceAlreadyExists(worldAddress, _namespace); - } - _world.registerNamespace(namespaceId); - } else if (!namespaceExists) { - revert WorldConsumer_NamespaceDoesNotExists(worldAddress, _namespace); - } + constructor(IBaseWorld _world) { + address worldAddress = address(_world); + StoreSwitch.setStoreAddress(worldAddress); } function _msgSender() public view virtual override returns (address sender) { @@ -79,4 +68,10 @@ abstract contract WorldConsumer is System { return WorldContextConsumer._msgValue(); } + + function checkWorldIsCaller(address world) internal view { + if (world != msg.sender) { + revert WorldConsumer_CallerIsNotWorld(world, msg.sender); + } + } } diff --git a/packages/world-consumer/test/WorldConsumer.t.sol b/packages/world-consumer/test/WorldConsumer.t.sol index 07e66baaae..d034db4b37 100644 --- a/packages/world-consumer/test/WorldConsumer.t.sol +++ b/packages/world-consumer/test/WorldConsumer.t.sol @@ -21,29 +21,26 @@ import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { WorldConsumer } from "../src/experimental/WorldConsumer.sol"; contract MockWorldConsumer is WorldConsumer { - constructor( - IBaseWorld world, - bytes14 namespace, - bool registerNamespace - ) WorldConsumer(world, namespace, registerNamespace) {} + bytes14 immutable namespace; + constructor(IBaseWorld world, bytes14 _namespace) WorldConsumer(world) { + namespace = _namespace; + } function getStoreAddress() public view virtual returns (address) { return StoreSwitch.getStoreAddress(); } function grantNamespaceAccess(address to) external { - IBaseWorld(_world()).grantAccess(namespaceId, to); - } - - function transferNamespaceOwnership(address to) external { - IBaseWorld(_world()).transferOwnership(namespaceId, to); + IBaseWorld(_world()).grantAccess(WorldResourceIdLib.encodeNamespace(namespace), to); } function callableByAnyone() external view {} function onlyCallableByWorld() external view onlyWorld {} - function onlyCallableByNamespace() external view onlyNamespace {} + function onlyCallableByNamespace() external view onlyNamespace(namespace) {} + + function onlyCallableByNamespaceOwner() external view onlyNamespaceOwner(namespace) {} function payableFn() external payable returns (uint256 value) { return _msgValue(); @@ -53,33 +50,33 @@ contract MockWorldConsumer is WorldConsumer { contract WorldConsumerTest is Test, GasReporter { using WorldResourceIdInstance for ResourceId; - function testWorldConsumer() public { - IBaseWorld world = createWorld(); - bytes14 namespace = "myNamespace"; - ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + bytes14 constant namespace = "myNamespace"; + bytes16 constant systemName = "mySystem"; - MockWorldConsumer mock = new MockWorldConsumer(world, namespace, true); - assertEq(mock.getStoreAddress(), address(world)); - assertEq(mock.namespace(), namespace); - assertEq(mock.namespaceId().unwrap(), namespaceId.unwrap()); + ResourceId systemId; + ResourceId namespaceId; + + IBaseWorld world; + MockWorldConsumer mock; + function setUp() public { + world = createWorld(); StoreSwitch.setStoreAddress(address(world)); - assertTrue(ResourceIds.getExists(namespaceId), "Namespace not registered"); + namespaceId = WorldResourceIdLib.encodeNamespace(namespace); + world.registerNamespace(namespaceId); + + systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, namespace, systemName); + + mock = new MockWorldConsumer(world, namespace); + } + + function testWorldConsumer() public view { + assertEq(mock.getStoreAddress(), address(world)); } // Test internal MUD access control function testAccessControl() public { - IBaseWorld world = createWorld(); - StoreSwitch.setStoreAddress(address(world)); - - bytes16 systemName = "mySystem"; - bytes14 namespace = "myNamespace"; - ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); - ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, namespace, systemName); - MockWorldConsumer mock = new MockWorldConsumer(world, namespace, true); - mock.transferNamespaceOwnership(address(this)); - // Register the mock as a system with PRIVATE access world.registerSystem(systemId, mock, false); @@ -96,15 +93,6 @@ contract WorldConsumerTest is Test, GasReporter { } function testOnlyWorld() public { - IBaseWorld world = createWorld(); - StoreSwitch.setStoreAddress(address(world)); - - bytes16 systemName = "mySystem"; - bytes14 namespace = "myNamespace"; - ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, namespace, systemName); - MockWorldConsumer mock = new MockWorldConsumer(world, namespace, true); - mock.transferNamespaceOwnership(address(this)); - // Register the mock as a system with PUBLIC access world.registerSystem(systemId, mock, true); @@ -119,16 +107,6 @@ contract WorldConsumerTest is Test, GasReporter { } function testOnlyNamespace() public { - IBaseWorld world = createWorld(); - StoreSwitch.setStoreAddress(address(world)); - - bytes16 systemName = "mySystem"; - bytes14 namespace = "myNamespace"; - ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); - ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, namespace, systemName); - MockWorldConsumer mock = new MockWorldConsumer(world, namespace, true); - mock.transferNamespaceOwnership(address(this)); - // Register the mock as a system with PUBLIC access world.registerSystem(systemId, mock, true); @@ -150,16 +128,37 @@ contract WorldConsumerTest is Test, GasReporter { world.call(systemId, abi.encodeCall(mock.onlyCallableByNamespace, ())); } - function testMsgValue() public { - IBaseWorld world = createWorld(); - StoreSwitch.setStoreAddress(address(world)); + function testOnlyNamespaceOwner() public { + // Register the mock as a system with PUBLIC access + world.registerSystem(systemId, mock, true); + + address alice = address(0x1234); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(WorldConsumer.WorldConsumer_CallerIsNotWorld.selector, world, alice)); + mock.onlyCallableByNamespaceOwner(); + + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(WorldConsumer.WorldConsumer_CallerIsNotNamespaceOwner.selector, world, namespace, alice) + ); + world.call(systemId, abi.encodeCall(mock.onlyCallableByNamespaceOwner, ())); + + // After granting access to namespace, it should not work + world.grantAccess(namespaceId, alice); + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(WorldConsumer.WorldConsumer_CallerIsNotNamespaceOwner.selector, world, namespace, alice) + ); + world.call(systemId, abi.encodeCall(mock.onlyCallableByNamespaceOwner, ())); - bytes16 systemName = "mySystem"; - bytes14 namespace = "myNamespace"; - ResourceId systemId = WorldResourceIdLib.encode(RESOURCE_SYSTEM, namespace, systemName); - MockWorldConsumer mock = new MockWorldConsumer(world, namespace, true); - mock.transferNamespaceOwnership(address(this)); + // After transfering namespace ownership, it should work + world.transferOwnership(namespaceId, alice); + vm.prank(alice); + world.call(systemId, abi.encodeCall(mock.onlyCallableByNamespaceOwner, ())); + } + function testMsgValue() public { // Register the mock as a system with PUBLIC access world.registerSystem(systemId, mock, true); From 175d2849901668a94d1e7981bf3cf600b02229ef Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 12 Feb 2025 16:22:50 -0300 Subject: [PATCH 02/10] Adapt ERC20 module --- .../src/examples/ERC20PausableBurnable.sol | 23 +++++++++++-------- .../src/experimental/ERC20Module.sol | 9 ++++++-- .../src/experimental/ERC20Pausable.sol | 2 ++ .../src/experimental/MUDERC20.sol | 21 +++++++++-------- .../src/experimental/Pausable.sol | 21 +++++++++-------- .../test/ERC20BaseTest.t.sol | 18 +++++++++++++-- .../world-module-erc20/test/ERC20Module.t.sol | 3 --- .../test/ERC20Pausable.t.sol | 9 ++++++-- 8 files changed, 69 insertions(+), 37 deletions(-) diff --git a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol index 30a9207b5f..e45a8fa07e 100644 --- a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol +++ b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol @@ -7,30 +7,35 @@ import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; +import { Pausable } from "../experimental/Pausable.sol"; import { ERC20Pausable } from "../experimental/ERC20Pausable.sol"; import { ERC20Burnable } from "../experimental/ERC20Burnable.sol"; import { MUDERC20 } from "../experimental/MUDERC20.sol"; contract ERC20PausableBurnable is MUDERC20, ERC20Pausable, ERC20Burnable { + bytes14 immutable namespace; + constructor( IBaseWorld world, - bytes14 namespace, - string memory name, - string memory symbol - ) WorldConsumer(world, namespace, true) MUDERC20(name, symbol) { - // Transfer namespace ownership to the creator - world.transferOwnership(namespaceId, _msgSender()); + bytes14 _namespace + ) WorldConsumer(world) MUDERC20(_namespace) ERC20Pausable(_namespace) { + namespace = _namespace; + } + + function initialize(string memory name, string memory symbol) external { + MUDERC20._init(name, symbol); + Pausable._init(); } - function mint(address to, uint256 value) public onlyNamespace { + function mint(address to, uint256 value) public onlyNamespace(namespace) { _mint(to, value); } - function pause() public onlyNamespace { + function pause() public onlyNamespace(namespace) { _pause(); } - function unpause() public onlyNamespace { + function unpause() public onlyNamespace(namespace) { _unpause(); } diff --git a/packages/world-module-erc20/src/experimental/ERC20Module.sol b/packages/world-module-erc20/src/experimental/ERC20Module.sol index b7b80d58df..804796ec43 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Module.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Module.sol @@ -39,11 +39,16 @@ contract ERC20Module is Module { IBaseWorld world = IBaseWorld(_world()); - ERC20PausableBurnable token = new ERC20PausableBurnable(world, namespace, name, symbol); + world.registerNamespace(namespaceId); - // Grant access to the token so it can write to tables after transferring ownership + ERC20PausableBurnable token = new ERC20PausableBurnable(world, namespace); + + // Grant access to the token so it can register and write to tables after transferring ownership world.grantAccess(namespaceId, address(token)); + // Register tables and set metadata + token.initialize(name, symbol); + // Register token as a system so its functions can be called through the world world.registerSystem(systemId, token, true); diff --git a/packages/world-module-erc20/src/experimental/ERC20Pausable.sol b/packages/world-module-erc20/src/experimental/ERC20Pausable.sol index 1dcf0da8e6..9774f0533e 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Pausable.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Pausable.sol @@ -6,6 +6,8 @@ import { Pausable } from "./Pausable.sol"; import { MUDERC20 } from "./MUDERC20.sol"; abstract contract ERC20Pausable is MUDERC20, Pausable { + constructor(bytes14 namespace) Pausable(namespace) {} + function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { super._update(from, to, value); } diff --git a/packages/world-module-erc20/src/experimental/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol index dec9cf64b6..36b81b8b4e 100644 --- a/packages/world-module-erc20/src/experimental/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -24,20 +24,11 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume ResourceId internal immutable allowancesId; ResourceId internal immutable metadataId; - constructor(string memory _name, string memory _symbol) { - // Needs to be inlined in the constructor + constructor(bytes14 namespace) { totalSupplyId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.TOTAL_SUPPLY); balancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.BALANCES); allowancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.ALLOWANCES); metadataId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.METADATA); - - // Register each table - TotalSupply.register(totalSupplyId); - Balances.register(balancesId); - Allowances.register(allowancesId); - ERC20Metadata.register(metadataId); - - _setMetadata(_name, _symbol, 18); } /** @@ -153,6 +144,16 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume return true; } + function _init(string memory _name, string memory _symbol) internal { + // Register each table + TotalSupply.register(totalSupplyId); + Balances.register(balancesId); + Allowances.register(allowancesId); + ERC20Metadata.register(metadataId); + + _setMetadata(_name, _symbol, 18); + } + /** * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). * Relies on the `_update` mechanism diff --git a/packages/world-module-erc20/src/experimental/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol index 70821ec9a4..0d046effc7 100644 --- a/packages/world-module-erc20/src/experimental/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -42,15 +42,6 @@ abstract contract Pausable is WorldConsumer { */ error ExpectedPause(); - /** - * @dev Initializes the contract in unpaused state. - */ - constructor() { - pausedId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, PausableTableNames.PAUSED); - PausedTable.register(pausedId); - PausedTable.set(pausedId, false); - } - /** * @dev Modifier to make a function callable only when the contract is not paused. * @@ -75,6 +66,18 @@ abstract contract Pausable is WorldConsumer { _; } + constructor(bytes14 namespace) { + pausedId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, PausableTableNames.PAUSED); + } + + /** + * @dev Initializes the contract in unpaused state. + */ + function _init() internal { + PausedTable.register(pausedId); + PausedTable.set(pausedId, false); + } + /** * @dev Returns true if the contract is paused, and false otherwise. */ diff --git a/packages/world-module-erc20/test/ERC20BaseTest.t.sol b/packages/world-module-erc20/test/ERC20BaseTest.t.sol index c92ecc01b8..e5b636df9b 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.t.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.t.sol @@ -6,9 +6,12 @@ import { console } from "forge-std/console.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; + import { createWorld } from "@latticexyz/world/test/createWorld.sol"; import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; +import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; @@ -26,7 +29,11 @@ library TestConstants { // Mock to include mint and burn functions contract MockERC20Base is MUDERC20 { - constructor() WorldConsumer(createWorld(), TestConstants.ERC20_NAMESPACE, true) MUDERC20("Token", "TKN") {} + constructor() WorldConsumer(createWorld()) MUDERC20(TestConstants.ERC20_NAMESPACE) {} + + function initialize() public virtual { + MUDERC20._init("Token", "TKN"); + } function __mint(address to, uint256 amount) public { _mint(to, amount); @@ -59,7 +66,14 @@ abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Er function setUp() public { token = createToken(); - StoreSwitch.setStoreAddress(token._world()); + address world = token._world(); + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE); + IBaseWorld(world).registerNamespace(namespaceId); + IBaseWorld(world).grantAccess(namespaceId, address(token)); + + token.initialize(); + + StoreSwitch.setStoreAddress(world); } function testSetUp() public { diff --git a/packages/world-module-erc20/test/ERC20Module.t.sol b/packages/world-module-erc20/test/ERC20Module.t.sol index ee176ef4d3..ecd734f3b0 100644 --- a/packages/world-module-erc20/test/ERC20Module.t.sol +++ b/packages/world-module-erc20/test/ERC20Module.t.sol @@ -62,9 +62,6 @@ contract ERC20ModuleTest is Test, GasReporter { // Module should transfer token namespace ownership to the creator assertEq(NamespaceOwner.get(erc20NamespaceId), address(this), "Token did not transfer ownership"); - assertEq(WorldConsumer(token).namespace(), TestConstants.ERC20_NAMESPACE); - assertEq(WorldConsumer(token).namespaceId().unwrap(), erc20NamespaceId.unwrap()); - vm.expectRevert(IModuleErrors.Module_AlreadyInstalled.selector); world.installModule(erc20Module, args); } diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol index 29c5e98430..d48110a447 100644 --- a/packages/world-module-erc20/test/ERC20Pausable.t.sol +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -12,10 +12,15 @@ import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; import { Pausable, ERC20Pausable } from "../src/experimental/ERC20Pausable.sol"; -import { MockERC20Base, ERC20BehaviorTest } from "./ERC20BaseTest.t.sol"; +import { TestConstants, MockERC20Base, ERC20BehaviorTest } from "./ERC20BaseTest.t.sol"; // Mock to include mint and burn functions -contract MockERC20Pausable is MockERC20Base, ERC20Pausable { +contract MockERC20Pausable is MockERC20Base, ERC20Pausable(TestConstants.ERC20_NAMESPACE) { + function initialize() public override { + MockERC20Base.initialize(); + Pausable._init(); + } + function pause() public { _pause(); } From 1c8735feefe92adfa1828bef63af98bdafa3a5fd Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 12 Feb 2025 16:57:04 -0300 Subject: [PATCH 03/10] Move table registration to module --- .../src/experimental/ERC20Module.sol | 40 ++++++++++++++----- .../src/experimental/MUDERC20.sol | 16 +++----- .../src/experimental/Pausable.sol | 7 +--- .../test/ERC20BaseTest.t.sol | 8 +++- 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/packages/world-module-erc20/src/experimental/ERC20Module.sol b/packages/world-module-erc20/src/experimental/ERC20Module.sol index 804796ec43..c0a2e7db4a 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Module.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Module.sol @@ -3,16 +3,22 @@ pragma solidity >=0.8.24; import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { Module } from "@latticexyz/world/src/Module.sol"; import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; -import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { ERC20Registry } from "../codegen/tables/ERC20Registry.sol"; import { ERC20PausableBurnable } from "../examples/ERC20PausableBurnable.sol"; -import { ModuleConstants } from "./Constants.sol"; +import { ModuleConstants, ERC20TableNames, PausableTableNames } from "./Constants.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 { Paused } from "../codegen/tables/Paused.sol"; contract ERC20Module is Module { error ERC20Module_InvalidNamespace(bytes14 namespace); @@ -41,26 +47,40 @@ contract ERC20Module is Module { world.registerNamespace(namespaceId); - ERC20PausableBurnable token = new ERC20PausableBurnable(world, namespace); + ERC20PausableBurnable token = ERC20ModuleLib.deployAndRegisterTables(world, namespace); // Grant access to the token so it can register and write to tables after transferring ownership world.grantAccess(namespaceId, address(token)); - // Register tables and set metadata + // Set metadata and paused state token.initialize(name, symbol); // Register token as a system so its functions can be called through the world world.registerSystem(systemId, token, true); - // The token should have transferred the namespace ownership to this module in its constructor - world.transferOwnership(namespaceId, _msgSender()); - - ERC20RegistryLib.register(world, namespaceId, address(token)); + ERC20ModuleLib.registerToken(world, namespaceId, address(token)); } } -library ERC20RegistryLib { - function register(IBaseWorld world, ResourceId namespaceId, address token) public { +library ERC20ModuleLib { + function deployAndRegisterTables(IBaseWorld world, bytes14 namespace) public returns (ERC20PausableBurnable) { + ResourceId totalSupplyId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.TOTAL_SUPPLY); + ResourceId balancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.BALANCES); + ResourceId allowancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.ALLOWANCES); + ResourceId metadataId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.METADATA); + ResourceId pausedId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, PausableTableNames.PAUSED); + + // Register each table + TotalSupply.register(totalSupplyId); + Balances.register(balancesId); + Allowances.register(allowancesId); + ERC20Metadata.register(metadataId); + Paused.register(pausedId); + + return new ERC20PausableBurnable(world, namespace); + } + + function registerToken(IBaseWorld world, ResourceId namespaceId, address token) public { ResourceId erc20RegistryTableId = ModuleConstants.registryTableId(); if (!ResourceIds.getExists(erc20RegistryTableId)) { world.registerNamespace(ModuleConstants.namespaceId()); diff --git a/packages/world-module-erc20/src/experimental/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol index 36b81b8b4e..ff9df9b43c 100644 --- a/packages/world-module-erc20/src/experimental/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -24,11 +24,11 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume ResourceId internal immutable allowancesId; ResourceId internal immutable metadataId; - constructor(bytes14 namespace) { - totalSupplyId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.TOTAL_SUPPLY); - balancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.BALANCES); - allowancesId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.ALLOWANCES); - metadataId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, ERC20TableNames.METADATA); + constructor(ResourceId _totalSupplyId, ResourceId _balancesId, ResourceId _allowancesId, ResourceId _metadataId) { + totalSupplyId = _totalSupplyId; + balancesId = _balancesId; + allowancesId = _allowancesId; + metadataId = _metadataId; } /** @@ -145,12 +145,6 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume } function _init(string memory _name, string memory _symbol) internal { - // Register each table - TotalSupply.register(totalSupplyId); - Balances.register(balancesId); - Allowances.register(allowancesId); - ERC20Metadata.register(metadataId); - _setMetadata(_name, _symbol, 18); } diff --git a/packages/world-module-erc20/src/experimental/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol index 0d046effc7..927f6a46a9 100644 --- a/packages/world-module-erc20/src/experimental/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -2,13 +2,11 @@ // Adapted from OpenZeppelin Contracts [utils/Pausable.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/utils/Pausable.sol) pragma solidity >=0.8.24; -import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; import { Paused as PausedTable } from "../codegen/tables/Paused.sol"; -import { PausableTableNames } from "./Constants.sol"; /** * @dev Contract module which allows children to implement an emergency stop @@ -66,15 +64,14 @@ abstract contract Pausable is WorldConsumer { _; } - constructor(bytes14 namespace) { - pausedId = WorldResourceIdLib.encode(RESOURCE_TABLE, namespace, PausableTableNames.PAUSED); + constructor(ResourceId _pausedId) { + pausedId = _pausedId; } /** * @dev Initializes the contract in unpaused state. */ function _init() internal { - PausedTable.register(pausedId); PausedTable.set(pausedId, false); } diff --git a/packages/world-module-erc20/test/ERC20BaseTest.t.sol b/packages/world-module-erc20/test/ERC20BaseTest.t.sol index e5b636df9b..e9bf2a0c2d 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.t.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.t.sol @@ -13,6 +13,7 @@ import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAcc import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; +import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; @@ -71,7 +72,12 @@ abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Er IBaseWorld(world).registerNamespace(namespaceId); IBaseWorld(world).grantAccess(namespaceId, address(token)); - token.initialize(); + // Register tables and set metadata + (bool success, bytes memory returnData) = address(token).delegatecall(abi.encodeCall(MockERC20Base.initialize, ())); + + if (!success) { + revertWithBytes(returnData); + } StoreSwitch.setStoreAddress(world); } From 0b3cb55c73404454858a4308ec5bda40b1eff126 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 12 Feb 2025 18:47:38 -0300 Subject: [PATCH 04/10] Make initialize onlyNamespaceOwner and adapt tests --- .../src/examples/ERC20PausableBurnable.sol | 14 ++++-- .../src/experimental/Constants.sol | 4 -- .../src/experimental/ERC20Module.sol | 11 +++-- .../src/experimental/ERC20Pausable.sol | 3 +- .../src/experimental/MUDERC20.sol | 10 ++-- .../src/experimental/Pausable.sol | 2 +- .../test/ERC20BaseTest.t.sol | 48 +++++++++++-------- .../test/ERC20Burnable.t.sol | 8 ++-- .../test/ERC20Pausable.t.sol | 16 +++++-- 9 files changed, 73 insertions(+), 43 deletions(-) diff --git a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol index e45a8fa07e..fc2036b8e2 100644 --- a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol +++ b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol @@ -13,16 +13,24 @@ import { ERC20Burnable } from "../experimental/ERC20Burnable.sol"; import { MUDERC20 } from "../experimental/MUDERC20.sol"; contract ERC20PausableBurnable is MUDERC20, ERC20Pausable, ERC20Burnable { + error ERC20PausableBurnable_AlreadyInitialized(); + bytes14 immutable namespace; constructor( IBaseWorld world, - bytes14 _namespace - ) WorldConsumer(world) MUDERC20(_namespace) ERC20Pausable(_namespace) { + bytes14 _namespace, + ResourceId _totalSupplyId, + ResourceId _balancesId, + ResourceId _allowancesId, + ResourceId _metadataId, + ResourceId _pausedId + ) WorldConsumer(world) MUDERC20(_totalSupplyId, _balancesId, _allowancesId, _metadataId) ERC20Pausable(_pausedId) { + // Namespace used for access control namespace = _namespace; } - function initialize(string memory name, string memory symbol) external { + function initialize(string memory name, string memory symbol) external onlyNamespaceOwner(namespace) { MUDERC20._init(name, symbol); Pausable._init(); } diff --git a/packages/world-module-erc20/src/experimental/Constants.sol b/packages/world-module-erc20/src/experimental/Constants.sol index 78a5490530..e41d008e56 100644 --- a/packages/world-module-erc20/src/experimental/Constants.sol +++ b/packages/world-module-erc20/src/experimental/Constants.sol @@ -27,10 +27,6 @@ library ERC20TableNames { bytes16 internal constant METADATA = "Metadata"; } -library OwnableTableNames { - bytes16 internal constant OWNER = "Owner"; -} - library PausableTableNames { bytes16 internal constant PAUSED = "Paused"; } diff --git a/packages/world-module-erc20/src/experimental/ERC20Module.sol b/packages/world-module-erc20/src/experimental/ERC20Module.sol index c0a2e7db4a..4e8b77c8a5 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Module.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Module.sol @@ -12,6 +12,7 @@ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld. import { ERC20Registry } from "../codegen/tables/ERC20Registry.sol"; import { ERC20PausableBurnable } from "../examples/ERC20PausableBurnable.sol"; +import { MUDERC20 } from "../experimental/MUDERC20.sol"; import { ModuleConstants, ERC20TableNames, PausableTableNames } from "./Constants.sol"; import { ERC20Metadata, ERC20MetadataData } from "../codegen/tables/ERC20Metadata.sol"; @@ -52,13 +53,15 @@ contract ERC20Module is Module { // Grant access to the token so it can register and write to tables after transferring ownership world.grantAccess(namespaceId, address(token)); - // Set metadata and paused state - token.initialize(name, symbol); - // Register token as a system so its functions can be called through the world world.registerSystem(systemId, token, true); + // Set metadata and paused state by calling initialize (onlyNamespaceOwner) + world.call(systemId, abi.encodeCall(ERC20PausableBurnable.initialize, (name, symbol))); + ERC20ModuleLib.registerToken(world, namespaceId, address(token)); + + world.transferOwnership(namespaceId, _msgSender()); } } @@ -77,7 +80,7 @@ library ERC20ModuleLib { ERC20Metadata.register(metadataId); Paused.register(pausedId); - return new ERC20PausableBurnable(world, namespace); + return new ERC20PausableBurnable(world, namespace, totalSupplyId, balancesId, allowancesId, metadataId, pausedId); } function registerToken(IBaseWorld world, ResourceId namespaceId, address token) public { diff --git a/packages/world-module-erc20/src/experimental/ERC20Pausable.sol b/packages/world-module-erc20/src/experimental/ERC20Pausable.sol index 9774f0533e..9af8c15436 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Pausable.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Pausable.sol @@ -2,11 +2,12 @@ // Adapted from OpenZeppelin's [ERC20Pausable extenstion](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/extensions/ERC20Pausable.sol) pragma solidity >=0.8.24; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { Pausable } from "./Pausable.sol"; import { MUDERC20 } from "./MUDERC20.sol"; abstract contract ERC20Pausable is MUDERC20, Pausable { - constructor(bytes14 namespace) Pausable(namespace) {} + constructor(ResourceId pausedId) Pausable(pausedId) {} function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { super._update(from, to, value); diff --git a/packages/world-module-erc20/src/experimental/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol index ff9df9b43c..ad99362739 100644 --- a/packages/world-module-erc20/src/experimental/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -2,6 +2,7 @@ // Adapted from OpenZeppelin Contracts [token/ERC20/ERC20.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/ERC20.sol) pragma solidity >=0.8.24; +import { console } from "forge-std/console.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; @@ -19,10 +20,10 @@ import { IERC20Errors } from "../interfaces/IERC20Errors.sol"; import { ERC20TableNames } from "./Constants.sol"; abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsumer { - ResourceId internal immutable totalSupplyId; - ResourceId internal immutable balancesId; - ResourceId internal immutable allowancesId; - ResourceId internal immutable metadataId; + ResourceId private immutable totalSupplyId; + ResourceId private immutable balancesId; + ResourceId private immutable allowancesId; + ResourceId private immutable metadataId; constructor(ResourceId _totalSupplyId, ResourceId _balancesId, ResourceId _allowancesId, ResourceId _metadataId) { totalSupplyId = _totalSupplyId; @@ -283,6 +284,7 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume } function _getSymbol() internal view returns (string memory) { + console.logBytes32(metadataId.unwrap()); return ERC20Metadata.getSymbol(metadataId); } diff --git a/packages/world-module-erc20/src/experimental/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol index 927f6a46a9..a8661b1baf 100644 --- a/packages/world-module-erc20/src/experimental/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -18,7 +18,7 @@ import { Paused as PausedTable } from "../codegen/tables/Paused.sol"; * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is WorldConsumer { - ResourceId internal immutable pausedId; + ResourceId private immutable pausedId; /** * @dev Emitted when the pause is triggered by `account`. diff --git a/packages/world-module-erc20/test/ERC20BaseTest.t.sol b/packages/world-module-erc20/test/ERC20BaseTest.t.sol index e9bf2a0c2d..05f8c71c43 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.t.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.t.sol @@ -7,6 +7,7 @@ import { console } from "forge-std/console.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { createWorld } from "@latticexyz/world/test/createWorld.sol"; import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAccess.sol"; @@ -24,13 +25,24 @@ import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; -library TestConstants { - bytes14 constant ERC20_NAMESPACE = "mockerc20ns"; -} +bytes14 constant namespace = "mockerc20ns"; + +ResourceId constant totalSupplyId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("TotalSupply"))) +); +ResourceId constant balancesId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Balances"))) +); +ResourceId constant allowancesId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Allowances"))) +); +ResourceId constant metadataId = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Metadata"))) +); // Mock to include mint and burn functions contract MockERC20Base is MUDERC20 { - constructor() WorldConsumer(createWorld()) MUDERC20(TestConstants.ERC20_NAMESPACE) {} + constructor(IBaseWorld world) WorldConsumer(world) MUDERC20(totalSupplyId, balancesId, allowancesId, metadataId) {} function initialize() public virtual { MUDERC20._init("Token", "TKN"); @@ -48,7 +60,7 @@ contract MockERC20Base is MUDERC20 { abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Errors { MockERC20Base token; - function createToken() internal virtual returns (MockERC20Base); + function createToken(IBaseWorld world) internal virtual returns (MockERC20Base); // Used for validating the addresses in fuzz tests function validAddress(address addr) internal view returns (bool) { @@ -66,20 +78,18 @@ abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Er } function setUp() public { - token = createToken(); - address world = token._world(); - ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE); - IBaseWorld(world).registerNamespace(namespaceId); - IBaseWorld(world).grantAccess(namespaceId, address(token)); + IBaseWorld world = createWorld(); + token = createToken(world); - // Register tables and set metadata - (bool success, bytes memory returnData) = address(token).delegatecall(abi.encodeCall(MockERC20Base.initialize, ())); + ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); - if (!success) { - revertWithBytes(returnData); - } + world.registerNamespace(namespaceId); + + world.grantAccess(namespaceId, address(token)); + + token.initialize(); - StoreSwitch.setStoreAddress(world); + StoreSwitch.setStoreAddress(address(world)); } function testSetUp() public { @@ -358,13 +368,13 @@ abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Er } function testNamespaceAccess() public { - assertTrue(ResourceAccess.get(WorldResourceIdLib.encodeNamespace(TestConstants.ERC20_NAMESPACE), address(token))); + assertTrue(ResourceAccess.get(WorldResourceIdLib.encodeNamespace(namespace), address(token))); } } // Concrete tests for basic namespace ERC20 behavior contract ERC20Test is ERC20BehaviorTest { - function createToken() internal virtual override returns (MockERC20Base) { - return new MockERC20Base(); + function createToken(IBaseWorld world) internal virtual override returns (MockERC20Base) { + return new MockERC20Base(world); } } diff --git a/packages/world-module-erc20/test/ERC20Burnable.t.sol b/packages/world-module-erc20/test/ERC20Burnable.t.sol index 248794e11d..998267b478 100644 --- a/packages/world-module-erc20/test/ERC20Burnable.t.sol +++ b/packages/world-module-erc20/test/ERC20Burnable.t.sol @@ -17,11 +17,13 @@ import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; import { ERC20Burnable } from "../src/experimental/ERC20Burnable.sol"; import { MockERC20Base, ERC20BehaviorTest } from "./ERC20BaseTest.t.sol"; -contract MockERC20Burnable is MockERC20Base, ERC20Burnable {} +contract MockERC20Burnable is MockERC20Base, ERC20Burnable { + constructor(IBaseWorld world) MockERC20Base(world) {} +} contract ERC20BurnableTest is ERC20BehaviorTest { - function createToken() internal override returns (MockERC20Base) { - return new MockERC20Burnable(); + function createToken(IBaseWorld world) internal override returns (MockERC20Base) { + return new MockERC20Burnable(world); } function testBurnByAccount() public { diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol index d48110a447..0343f21f0e 100644 --- a/packages/world-module-erc20/test/ERC20Pausable.t.sol +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -5,6 +5,10 @@ import { Test } from "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; import { GasReporter } from "@latticexyz/gas-report/src/GasReporter.sol"; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; + import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; @@ -12,10 +16,14 @@ import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; import { Pausable, ERC20Pausable } from "../src/experimental/ERC20Pausable.sol"; -import { TestConstants, MockERC20Base, ERC20BehaviorTest } from "./ERC20BaseTest.t.sol"; +import { MockERC20Base, ERC20BehaviorTest, namespace } from "./ERC20BaseTest.t.sol"; + +ResourceId constant pausedId = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Paused")))); // Mock to include mint and burn functions -contract MockERC20Pausable is MockERC20Base, ERC20Pausable(TestConstants.ERC20_NAMESPACE) { +contract MockERC20Pausable is MockERC20Base, ERC20Pausable(pausedId) { + constructor(IBaseWorld world) MockERC20Base(world) {} + function initialize() public override { MockERC20Base.initialize(); Pausable._init(); @@ -35,8 +43,8 @@ contract MockERC20Pausable is MockERC20Base, ERC20Pausable(TestConstants.ERC20_N } abstract contract ERC20PausableBehaviorTest is ERC20BehaviorTest { - function createToken() internal override returns (MockERC20Base) { - return new MockERC20Pausable(); + function createToken(IBaseWorld world) internal override returns (MockERC20Base) { + return new MockERC20Pausable(world); } function testPause() public { From db18f29403dcf9ffbb439e83f6e5d2d0e94cb391 Mon Sep 17 00:00:00 2001 From: vdrg Date: Wed, 12 Feb 2025 18:50:53 -0300 Subject: [PATCH 05/10] Use unique names for init functions --- .../world-module-erc20/src/examples/ERC20PausableBurnable.sol | 4 ++-- packages/world-module-erc20/src/experimental/MUDERC20.sol | 4 +--- packages/world-module-erc20/src/experimental/Pausable.sol | 2 +- packages/world-module-erc20/test/ERC20BaseTest.t.sol | 2 +- packages/world-module-erc20/test/ERC20Pausable.t.sol | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol index fc2036b8e2..9822c3bd7e 100644 --- a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol +++ b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol @@ -31,8 +31,8 @@ contract ERC20PausableBurnable is MUDERC20, ERC20Pausable, ERC20Burnable { } function initialize(string memory name, string memory symbol) external onlyNamespaceOwner(namespace) { - MUDERC20._init(name, symbol); - Pausable._init(); + _MUDERC20_init(name, symbol); + _Pausable_init(); } function mint(address to, uint256 value) public onlyNamespace(namespace) { diff --git a/packages/world-module-erc20/src/experimental/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol index ad99362739..3cedb99118 100644 --- a/packages/world-module-erc20/src/experimental/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -2,7 +2,6 @@ // Adapted from OpenZeppelin Contracts [token/ERC20/ERC20.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/f989fff93168606c726bc5e831ef50dd6e543f45/contracts/token/ERC20/ERC20.sol) pragma solidity >=0.8.24; -import { console } from "forge-std/console.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; @@ -145,7 +144,7 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume return true; } - function _init(string memory _name, string memory _symbol) internal { + function _MUDERC20_init(string memory _name, string memory _symbol) internal { _setMetadata(_name, _symbol, 18); } @@ -284,7 +283,6 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume } function _getSymbol() internal view returns (string memory) { - console.logBytes32(metadataId.unwrap()); return ERC20Metadata.getSymbol(metadataId); } diff --git a/packages/world-module-erc20/src/experimental/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol index a8661b1baf..1f9d54bae5 100644 --- a/packages/world-module-erc20/src/experimental/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -71,7 +71,7 @@ abstract contract Pausable is WorldConsumer { /** * @dev Initializes the contract in unpaused state. */ - function _init() internal { + function _Pausable_init() internal { PausedTable.set(pausedId, false); } diff --git a/packages/world-module-erc20/test/ERC20BaseTest.t.sol b/packages/world-module-erc20/test/ERC20BaseTest.t.sol index 05f8c71c43..84866ad061 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.t.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.t.sol @@ -45,7 +45,7 @@ contract MockERC20Base is MUDERC20 { constructor(IBaseWorld world) WorldConsumer(world) MUDERC20(totalSupplyId, balancesId, allowancesId, metadataId) {} function initialize() public virtual { - MUDERC20._init("Token", "TKN"); + _MUDERC20_init("Token", "TKN"); } function __mint(address to, uint256 amount) public { diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol index 0343f21f0e..dcfad59b47 100644 --- a/packages/world-module-erc20/test/ERC20Pausable.t.sol +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -26,7 +26,7 @@ contract MockERC20Pausable is MockERC20Base, ERC20Pausable(pausedId) { function initialize() public override { MockERC20Base.initialize(); - Pausable._init(); + _Pausable_init(); } function pause() public { From 49384fd576a87684486288aaf0036b4fb65ad623 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 13 Feb 2025 09:25:49 -0300 Subject: [PATCH 06/10] Fix tests --- .../test/ERC20BaseTest.t.sol | 22 +++++++++++++++---- .../world-module-erc20/test/ERC20Module.t.sol | 4 ++++ .../test/ERC20Pausable.t.sol | 8 +++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/world-module-erc20/test/ERC20BaseTest.t.sol b/packages/world-module-erc20/test/ERC20BaseTest.t.sol index 84866ad061..450a744d1d 100644 --- a/packages/world-module-erc20/test/ERC20BaseTest.t.sol +++ b/packages/world-module-erc20/test/ERC20BaseTest.t.sol @@ -18,7 +18,12 @@ import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; -import { ERC20MetadataData } from "../src/codegen/tables/ERC20Metadata.sol"; +import { ERC20Metadata } from "../src/codegen/tables/ERC20Metadata.sol"; +import { TotalSupply } from "../src/codegen/tables/TotalSupply.sol"; +import { Balances } from "../src/codegen/tables/Balances.sol"; +import { Allowances } from "../src/codegen/tables/Allowances.sol"; +import { Paused } from "../src/codegen/tables/Paused.sol"; + import { IERC20 } from "../src/interfaces/IERC20.sol"; import { IERC20Metadata } from "../src/interfaces/IERC20Metadata.sol"; import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; @@ -39,6 +44,7 @@ ResourceId constant allowancesId = ResourceId.wrap( ResourceId constant metadataId = ResourceId.wrap( bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Metadata"))) ); +ResourceId constant pausedId = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Paused")))); // Mock to include mint and burn functions contract MockERC20Base is MUDERC20 { @@ -79,17 +85,25 @@ abstract contract ERC20BehaviorTest is Test, GasReporter, IERC20Events, IERC20Er function setUp() public { IBaseWorld world = createWorld(); - token = createToken(world); + + StoreSwitch.setStoreAddress(address(world)); ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace); world.registerNamespace(namespaceId); + // Register each table + TotalSupply.register(totalSupplyId); + Balances.register(balancesId); + Allowances.register(allowancesId); + ERC20Metadata.register(metadataId); + Paused.register(pausedId); + + token = createToken(world); + world.grantAccess(namespaceId, address(token)); token.initialize(); - - StoreSwitch.setStoreAddress(address(world)); } function testSetUp() public { diff --git a/packages/world-module-erc20/test/ERC20Module.t.sol b/packages/world-module-erc20/test/ERC20Module.t.sol index ecd734f3b0..2ab9a32f97 100644 --- a/packages/world-module-erc20/test/ERC20Module.t.sol +++ b/packages/world-module-erc20/test/ERC20Module.t.sol @@ -20,6 +20,7 @@ import { ResourceAccess } from "@latticexyz/world/src/codegen/tables/ResourceAcc import { WorldConsumer } from "@latticexyz/world-consumer/src/experimental/WorldConsumer.sol"; import { ModuleConstants } from "../src/experimental/Constants.sol"; +import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; import { ERC20Module } from "../src/experimental/ERC20Module.sol"; import { ERC20Registry } from "../src/codegen/tables/ERC20Registry.sol"; @@ -62,6 +63,9 @@ contract ERC20ModuleTest is Test, GasReporter { // Module should transfer token namespace ownership to the creator assertEq(NamespaceOwner.get(erc20NamespaceId), address(this), "Token did not transfer ownership"); + assertEq(MUDERC20(token).name(), "myERC20Token"); + assertEq(MUDERC20(token).symbol(), "MTK"); + vm.expectRevert(IModuleErrors.Module_AlreadyInstalled.selector); world.installModule(erc20Module, args); } diff --git a/packages/world-module-erc20/test/ERC20Pausable.t.sol b/packages/world-module-erc20/test/ERC20Pausable.t.sol index dcfad59b47..78a573283a 100644 --- a/packages/world-module-erc20/test/ERC20Pausable.t.sol +++ b/packages/world-module-erc20/test/ERC20Pausable.t.sol @@ -16,16 +16,14 @@ import { IERC20Errors } from "../src/interfaces/IERC20Errors.sol"; import { IERC20Events } from "../src/interfaces/IERC20Events.sol"; import { MUDERC20 } from "../src/experimental/MUDERC20.sol"; import { Pausable, ERC20Pausable } from "../src/experimental/ERC20Pausable.sol"; -import { MockERC20Base, ERC20BehaviorTest, namespace } from "./ERC20BaseTest.t.sol"; - -ResourceId constant pausedId = ResourceId.wrap(bytes32(abi.encodePacked(RESOURCE_TABLE, namespace, bytes16("Paused")))); +import { MockERC20Base, ERC20BehaviorTest, namespace, pausedId } from "./ERC20BaseTest.t.sol"; // Mock to include mint and burn functions contract MockERC20Pausable is MockERC20Base, ERC20Pausable(pausedId) { constructor(IBaseWorld world) MockERC20Base(world) {} function initialize() public override { - MockERC20Base.initialize(); + super.initialize(); _Pausable_init(); } @@ -42,7 +40,7 @@ contract MockERC20Pausable is MockERC20Base, ERC20Pausable(pausedId) { } } -abstract contract ERC20PausableBehaviorTest is ERC20BehaviorTest { +contract ERC20PausableTest is ERC20BehaviorTest { function createToken(IBaseWorld world) internal override returns (MockERC20Base) { return new MockERC20Pausable(world); } From 9ecbbd764aeac99d0575ba29d017a2258956a001 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 13 Feb 2025 09:39:30 -0300 Subject: [PATCH 07/10] gas report --- packages/world-module-erc20/gas-report.json | 66 +++++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/world-module-erc20/gas-report.json b/packages/world-module-erc20/gas-report.json index 85ac764278..ae3906cff4 100644 --- a/packages/world-module-erc20/gas-report.json +++ b/packages/world-module-erc20/gas-report.json @@ -3,72 +3,114 @@ "file": "test/ERC20BaseTest.t.sol:ERC20Test", "test": "testApprove", "name": "world_approve", - "gasUsed": 71093 + "gasUsed": 71092 }, { "file": "test/ERC20BaseTest.t.sol:ERC20Test", "test": "testBurn", "name": "world_burn", - "gasUsed": 70710 + "gasUsed": 70708 }, { "file": "test/ERC20BaseTest.t.sol:ERC20Test", "test": "testMint", "name": "world_mint", - "gasUsed": 109431 + "gasUsed": 109495 }, { "file": "test/ERC20BaseTest.t.sol:ERC20Test", "test": "testTransfer", "name": "world_transfer", - "gasUsed": 82487 + "gasUsed": 82440 }, { "file": "test/ERC20BaseTest.t.sol:ERC20Test", "test": "testTransferFrom", "name": "world_transferFrom", - "gasUsed": 99471 + "gasUsed": 99468 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testApprove", "name": "world_approve", - "gasUsed": 71093 + "gasUsed": 71092 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testBurn", "name": "world_burn", - "gasUsed": 70711 + "gasUsed": 70709 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testBurnByAccount", "name": "world_burn", - "gasUsed": 71099 + "gasUsed": 71140 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testMint", "name": "world_mint", - "gasUsed": 109497 + "gasUsed": 109473 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testTransfer", "name": "world_transfer", - "gasUsed": 82487 + "gasUsed": 82485 }, { "file": "test/ERC20Burnable.t.sol:ERC20BurnableTest", "test": "testTransferFrom", "name": "world_transferFrom", - "gasUsed": 99494 + "gasUsed": 99491 }, { "file": "test/ERC20Module.t.sol:ERC20ModuleTest", "test": "testInstall", "name": "install erc20 module", - "gasUsed": 4798590 + "gasUsed": 5217834 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testApprove", + "name": "world_approve", + "gasUsed": 71093 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testBurn", + "name": "world_burn", + "gasUsed": 75392 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testMint", + "name": "world_mint", + "gasUsed": 114157 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testPause", + "name": "world_pause", + "gasUsed": 70854 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testPause", + "name": "world_unpause", + "gasUsed": 44450 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testTransfer", + "name": "world_transfer", + "gasUsed": 87169 + }, + { + "file": "test/ERC20Pausable.t.sol:ERC20PausableTest", + "test": "testTransferFrom", + "name": "world_transferFrom", + "gasUsed": 104177 } ] From 08eb7048a422b5389a2deb1d581e393f192cdb2f Mon Sep 17 00:00:00 2001 From: V Date: Thu, 13 Feb 2025 09:42:54 -0300 Subject: [PATCH 08/10] Create rich-islands-stare.md --- .changeset/rich-islands-stare.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/rich-islands-stare.md diff --git a/.changeset/rich-islands-stare.md b/.changeset/rich-islands-stare.md new file mode 100644 index 0000000000..5882187f8a --- /dev/null +++ b/.changeset/rich-islands-stare.md @@ -0,0 +1,8 @@ +--- +"@latticexyz/world-consumer": patch +"@latticexyz/world-module-erc20": patch +--- + +`WorldConsumer` now doesn't store a single namespace. Instead, child contracts can keep track of namespaces and use the `onlyNamespace(namespace)` and `onlyNamespaceOwner(namespace)` modifiers for access control. + +ERC20 module was adapted to use this new version of `WorldConsumer`. From 954c34b77901b871926192cdc8ef364fec9ddc60 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 13 Feb 2025 09:45:45 -0300 Subject: [PATCH 09/10] Remove unused function in tests --- packages/world-consumer/test/WorldConsumer.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/world-consumer/test/WorldConsumer.t.sol b/packages/world-consumer/test/WorldConsumer.t.sol index d034db4b37..429c96a7b7 100644 --- a/packages/world-consumer/test/WorldConsumer.t.sol +++ b/packages/world-consumer/test/WorldConsumer.t.sol @@ -30,10 +30,6 @@ contract MockWorldConsumer is WorldConsumer { return StoreSwitch.getStoreAddress(); } - function grantNamespaceAccess(address to) external { - IBaseWorld(_world()).grantAccess(WorldResourceIdLib.encodeNamespace(namespace), to); - } - function callableByAnyone() external view {} function onlyCallableByWorld() external view onlyWorld {} From 13617f79dfb6dd87f2ef8b92bd3f6952c56276f1 Mon Sep 17 00:00:00 2001 From: vdrg Date: Thu, 13 Feb 2025 10:26:30 -0300 Subject: [PATCH 10/10] Use named parameters in module --- .../src/examples/ERC20PausableBurnable.sol | 26 ++++++------ .../src/experimental/ERC20Module.sol | 11 ++++- .../src/experimental/MUDERC20.sol | 40 +++++++++---------- .../src/experimental/Pausable.sol | 14 +++---- 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol index 9822c3bd7e..31035f5ca6 100644 --- a/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol +++ b/packages/world-module-erc20/src/examples/ERC20PausableBurnable.sol @@ -15,35 +15,35 @@ import { MUDERC20 } from "../experimental/MUDERC20.sol"; contract ERC20PausableBurnable is MUDERC20, ERC20Pausable, ERC20Burnable { error ERC20PausableBurnable_AlreadyInitialized(); - bytes14 immutable namespace; + bytes14 private immutable _namespace; constructor( IBaseWorld world, - bytes14 _namespace, - ResourceId _totalSupplyId, - ResourceId _balancesId, - ResourceId _allowancesId, - ResourceId _metadataId, - ResourceId _pausedId - ) WorldConsumer(world) MUDERC20(_totalSupplyId, _balancesId, _allowancesId, _metadataId) ERC20Pausable(_pausedId) { + bytes14 namespace, + ResourceId totalSupplyId, + ResourceId balancesId, + ResourceId allowancesId, + ResourceId metadataId, + ResourceId pausedId + ) WorldConsumer(world) MUDERC20(totalSupplyId, balancesId, allowancesId, metadataId) ERC20Pausable(pausedId) { // Namespace used for access control - namespace = _namespace; + _namespace = namespace; } - function initialize(string memory name, string memory symbol) external onlyNamespaceOwner(namespace) { + function initialize(string memory name, string memory symbol) external onlyNamespaceOwner(_namespace) { _MUDERC20_init(name, symbol); _Pausable_init(); } - function mint(address to, uint256 value) public onlyNamespace(namespace) { + function mint(address to, uint256 value) public onlyNamespace(_namespace) { _mint(to, value); } - function pause() public onlyNamespace(namespace) { + function pause() public onlyNamespace(_namespace) { _pause(); } - function unpause() public onlyNamespace(namespace) { + function unpause() public onlyNamespace(_namespace) { _unpause(); } diff --git a/packages/world-module-erc20/src/experimental/ERC20Module.sol b/packages/world-module-erc20/src/experimental/ERC20Module.sol index 4e8b77c8a5..717a9536a4 100644 --- a/packages/world-module-erc20/src/experimental/ERC20Module.sol +++ b/packages/world-module-erc20/src/experimental/ERC20Module.sol @@ -80,7 +80,16 @@ library ERC20ModuleLib { ERC20Metadata.register(metadataId); Paused.register(pausedId); - return new ERC20PausableBurnable(world, namespace, totalSupplyId, balancesId, allowancesId, metadataId, pausedId); + return + new ERC20PausableBurnable({ + world: world, + namespace: namespace, + totalSupplyId: totalSupplyId, + balancesId: balancesId, + allowancesId: allowancesId, + metadataId: metadataId, + pausedId: pausedId + }); } function registerToken(IBaseWorld world, ResourceId namespaceId, address token) public { diff --git a/packages/world-module-erc20/src/experimental/MUDERC20.sol b/packages/world-module-erc20/src/experimental/MUDERC20.sol index 3cedb99118..ce528ecae7 100644 --- a/packages/world-module-erc20/src/experimental/MUDERC20.sol +++ b/packages/world-module-erc20/src/experimental/MUDERC20.sol @@ -19,16 +19,16 @@ import { IERC20Errors } from "../interfaces/IERC20Errors.sol"; import { ERC20TableNames } from "./Constants.sol"; abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsumer { - ResourceId private immutable totalSupplyId; - ResourceId private immutable balancesId; - ResourceId private immutable allowancesId; - ResourceId private immutable metadataId; - - constructor(ResourceId _totalSupplyId, ResourceId _balancesId, ResourceId _allowancesId, ResourceId _metadataId) { - totalSupplyId = _totalSupplyId; - balancesId = _balancesId; - allowancesId = _allowancesId; - metadataId = _metadataId; + ResourceId private immutable _totalSupplyId; + ResourceId private immutable _balancesId; + ResourceId private immutable _allowancesId; + ResourceId private immutable _metadataId; + + constructor(ResourceId totalSupplyId, ResourceId balancesId, ResourceId allowancesId, ResourceId metadataId) { + _totalSupplyId = totalSupplyId; + _balancesId = balancesId; + _allowancesId = allowancesId; + _metadataId = metadataId; } /** @@ -279,43 +279,43 @@ abstract contract MUDERC20 is IERC20, IERC20Metadata, IERC20Errors, WorldConsume } function _getName() internal view returns (string memory) { - return ERC20Metadata.getName(metadataId); + return ERC20Metadata.getName(_metadataId); } function _getSymbol() internal view returns (string memory) { - return ERC20Metadata.getSymbol(metadataId); + return ERC20Metadata.getSymbol(_metadataId); } function _getDecimals() internal view returns (uint8) { - return ERC20Metadata.getDecimals(metadataId); + return ERC20Metadata.getDecimals(_metadataId); } function _getTotalSupply() internal view returns (uint256) { - return TotalSupply.get(totalSupplyId); + return TotalSupply.get(_totalSupplyId); } function _getBalance(address account) internal view returns (uint256) { - return Balances.get(balancesId, account); + return Balances.get(_balancesId, account); } function _getAllowance(address owner, address spender) internal view returns (uint256) { - return Allowances.get(allowancesId, owner, spender); + return Allowances.get(_allowancesId, owner, spender); } function _setTotalSupply(uint256 value) internal virtual { - TotalSupply.set(totalSupplyId, value); + TotalSupply.set(_totalSupplyId, value); } function _setBalance(address account, uint256 value) internal virtual { - Balances.set(balancesId, account, value); + Balances.set(_balancesId, account, value); } function _setAllowance(address owner, address spender, uint256 value) internal virtual { - Allowances.set(allowancesId, owner, spender, value); + Allowances.set(_allowancesId, owner, spender, value); } function _setMetadata(string memory _name, string memory _symbol, uint8 _decimals) internal virtual { ERC20MetadataData memory metadata = ERC20MetadataData(_decimals, _name, _symbol); - ERC20Metadata.set(metadataId, metadata); + ERC20Metadata.set(_metadataId, metadata); } } diff --git a/packages/world-module-erc20/src/experimental/Pausable.sol b/packages/world-module-erc20/src/experimental/Pausable.sol index 1f9d54bae5..bc6814fe82 100644 --- a/packages/world-module-erc20/src/experimental/Pausable.sol +++ b/packages/world-module-erc20/src/experimental/Pausable.sol @@ -18,7 +18,7 @@ import { Paused as PausedTable } from "../codegen/tables/Paused.sol"; * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is WorldConsumer { - ResourceId private immutable pausedId; + ResourceId private immutable _pausedId; /** * @dev Emitted when the pause is triggered by `account`. @@ -64,22 +64,22 @@ abstract contract Pausable is WorldConsumer { _; } - constructor(ResourceId _pausedId) { - pausedId = _pausedId; + constructor(ResourceId pausedId) { + _pausedId = pausedId; } /** * @dev Initializes the contract in unpaused state. */ function _Pausable_init() internal { - PausedTable.set(pausedId, false); + PausedTable.set(_pausedId, false); } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { - return PausedTable.get(pausedId); + return PausedTable.get(_pausedId); } /** @@ -108,7 +108,7 @@ abstract contract Pausable is WorldConsumer { * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { - PausedTable.set(pausedId, true); + PausedTable.set(_pausedId, true); emit Paused(_msgSender()); } @@ -120,7 +120,7 @@ abstract contract Pausable is WorldConsumer { * - The contract must be paused. */ function _unpause() internal virtual whenPaused { - PausedTable.set(pausedId, false); + PausedTable.set(_pausedId, false); emit Unpaused(_msgSender()); } }