Skip to content

Commit

Permalink
feat(world-modules): add ERC20 module (#1789)
Browse files Browse the repository at this point in the history
Co-authored-by: Fraser Scott <[email protected]>
Co-authored-by: 0xhank <[email protected]>
  • Loading branch information
3 people authored Nov 1, 2023
1 parent 35348f8 commit 8363837
Show file tree
Hide file tree
Showing 22 changed files with 2,666 additions and 2 deletions.
20 changes: 20 additions & 0 deletions .changeset/silent-buttons-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@latticexyz/world-modules": minor
---

Added the `ERC20Module` to `@latticexyz/world-modules`.
This module allows the registration of `ERC20` tokens in an existing World.

Important note: this module has not been audited yet, so any production use is discouraged for now.

```solidity
import { PuppetModule } from "@latticexyz/world-modules/src/modules/puppet/PuppetModule.sol";
import { IERC20Mintable } from "@latticexyz/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol";
import { registerERC20 } from "@latticexyz/world-modules/src/modules/erc20-puppet/registerERC20.sol";
// The ERC20 module requires the Puppet module to be installed first
world.installModule(new PuppetModule(), new bytes(0));
// After the Puppet module is installed, new ERC20 tokens can be registered
IERC20Mintable token = registerERC20(world, "myERC20", ERC20MetadataData({ decimals: 18, name: "Token", symbol: "TKN" }));
```
30 changes: 30 additions & 0 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
[
{
"file": "test/ERC20.t.sol",
"test": "testApprove",
"name": "approve",
"gasUsed": 114329
},
{
"file": "test/ERC20.t.sol",
"test": "testBurn",
"name": "burn",
"gasUsed": 75866
},
{
"file": "test/ERC20.t.sol",
"test": "testMint",
"name": "mint",
"gasUsed": 161705
},
{
"file": "test/ERC20.t.sol",
"test": "testTransfer",
"name": "transfer",
"gasUsed": 92948
},
{
"file": "test/ERC20.t.sol",
"test": "testTransferFrom",
"name": "transferFrom",
"gasUsed": 130250
},
{
"file": "test/KeysInTableModule.t.sol",
"test": "testInstallComposite",
Expand Down
56 changes: 55 additions & 1 deletion packages/world-modules/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,61 @@ export default mudConfig({
},
tableIdArgument: true,
},
/************************************************************************
*
* ERC20 MODULE
*
************************************************************************/
Balances: {
directory: "modules/erc20-puppet/tables",
keySchema: {
account: "address",
},
valueSchema: {
value: "uint256",
},
tableIdArgument: true,
},
Allowances: {
directory: "modules/erc20-puppet/tables",
keySchema: {
account: "address",
spender: "address",
},
valueSchema: {
value: "uint256",
},
tableIdArgument: true,
},
TotalSupply: {
directory: "modules/erc20-puppet/tables",
keySchema: {},
valueSchema: {
totalSupply: "uint256",
},
tableIdArgument: true,
},
Metadata: {
directory: "modules/erc20-puppet/tables",
keySchema: {},
valueSchema: {
decimals: "uint8",
name: "string",
symbol: "string",
},
tableIdArgument: true,
},
ERC20Registry: {
directory: "modules/erc20-puppet/tables",
keySchema: {
namespaceId: "ResourceId",
},
valueSchema: {
erc20Address: "address",
},
tableIdArgument: true,
},
},

excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem"],
excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System"],
});
2 changes: 1 addition & 1 deletion packages/world-modules/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ds-test/=node_modules/ds-test/src/
forge-std/=node_modules/forge-std/src/
@latticexyz/=node_modules/@latticexyz/
@latticexyz/=node_modules/@latticexyz/
5 changes: 5 additions & 0 deletions packages/world-modules/src/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ import { UniqueEntity } from "./modules/uniqueentity/tables/UniqueEntity.sol";
import { CallboundDelegations, CallboundDelegationsTableId } from "./modules/std-delegations/tables/CallboundDelegations.sol";
import { TimeboundDelegations, TimeboundDelegationsTableId } from "./modules/std-delegations/tables/TimeboundDelegations.sol";
import { PuppetRegistry } from "./modules/puppet/tables/PuppetRegistry.sol";
import { Balances } from "./modules/erc20-puppet/tables/Balances.sol";
import { Allowances } from "./modules/erc20-puppet/tables/Allowances.sol";
import { TotalSupply } from "./modules/erc20-puppet/tables/TotalSupply.sol";
import { Metadata, MetadataData } from "./modules/erc20-puppet/tables/Metadata.sol";
import { ERC20Registry } from "./modules/erc20-puppet/tables/ERC20Registry.sol";
32 changes: 32 additions & 0 deletions packages/world-modules/src/interfaces/IERC20System.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;

/* Autogenerated file. Do not edit manually. */

/**
* @title IERC20System
* @dev This interface is automatically generated from the corresponding system contract. Do not edit manually.
*/
interface IERC20System {
function name() external view returns (string memory);

function symbol() external view returns (string memory);

function decimals() external view returns (uint8);

function totalSupply() external view returns (uint256);

function balanceOf(address account) external view returns (uint256);

function allowance(address owner, address spender) external view returns (uint256);

function transfer(address to, uint256 value) external returns (bool);

function approve(address spender, uint256 value) external returns (bool);

function transferFrom(address from, address to, uint256 value) external returns (bool);

function mint(address account, uint256 value) external;

function burn(address account, uint256 value) external;
}
93 changes: 93 additions & 0 deletions packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;

import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol";
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { Module } from "@latticexyz/world/src/Module.sol";
import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol";
import { InstalledModules } from "@latticexyz/world/src/codegen/tables/InstalledModules.sol";

import { Puppet } from "../puppet/Puppet.sol";
import { createPuppet } from "../puppet/createPuppet.sol";
import { MODULE_NAME as PUPPET_MODULE_NAME } from "../puppet/constants.sol";

import { MODULE_NAME, MODULE_NAMESPACE, MODULE_NAMESPACE_ID, ERC20_REGISTRY_TABLE_ID } from "./constants.sol";
import { _allowancesTableId, _balancesTableId, _metadataTableId, _erc20SystemId } from "./utils.sol";
import { ERC20System } from "./ERC20System.sol";

import { ERC20Registry } from "./tables/ERC20Registry.sol";
import { Balances } from "./tables/Balances.sol";
import { Allowances } from "./tables/Allowances.sol";
import { Metadata, MetadataData } from "./tables/Metadata.sol";

contract ERC20Module is Module {
error ERC20Module_InvalidNamespace(bytes14 namespace);

function getName() public pure override returns (bytes16) {
return MODULE_NAME;
}

/**
* Register systems and tables for a new ERC20 token in a given namespace
*/
function _registerERC20(bytes14 namespace) internal {
// Register the tables
Allowances.register(_allowancesTableId(namespace));
Balances.register(_balancesTableId(namespace));
Metadata.register(_metadataTableId(namespace));

// Register a new ERC20System
IBaseWorld(_world()).registerSystem(_erc20SystemId(namespace), new ERC20System(), true);
}

function _requireDependencies() internal view {
// If the PuppetModule is not installed yet, install it
if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) {
revert Module_MissingDependency(string(bytes.concat(PUPPET_MODULE_NAME)));
}
}

function install(bytes memory args) public {
// Require the module to not be installed with these args yet
if (InstalledModules.get(MODULE_NAME, keccak256(args)) != address(0)) {
revert Module_AlreadyInstalled();
}

// Extract args
(bytes14 namespace, MetadataData memory metadata) = abi.decode(args, (bytes14, MetadataData));

// Require the namespace to not be the module's namespace
if (namespace == MODULE_NAMESPACE) {
revert ERC20Module_InvalidNamespace(namespace);
}

// Require dependencies
_requireDependencies();

// Register the ERC20 tables and system
_registerERC20(namespace);

// Initialize the Metadata
Metadata.set(_metadataTableId(namespace), metadata);

// Deploy and register the ERC20 puppet.
IBaseWorld world = IBaseWorld(_world());
ResourceId erc20SystemId = _erc20SystemId(namespace);
address puppet = createPuppet(world, erc20SystemId);

// Transfer ownership of the namespace to the caller
ResourceId namespaceId = WorldResourceIdLib.encodeNamespace(namespace);
world.transferOwnership(namespaceId, _msgSender());

// Register the ERC20 in the ERC20Registry
if (!ResourceIds.getExists(ERC20_REGISTRY_TABLE_ID)) {
ERC20Registry.register(ERC20_REGISTRY_TABLE_ID);
}
ERC20Registry.set(ERC20_REGISTRY_TABLE_ID, namespaceId, puppet);
}

function installRoot(bytes memory) public pure {
revert Module_RootInstallNotSupported();
}
}
Loading

0 comments on commit 8363837

Please sign in to comment.