-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(world-modules): add ERC20 module #1789
Changes from all commits
196425a
61947dd
8afae84
3b686f0
67efa1c
50e3bba
d86c433
9062ab4
191676b
44d1418
8b814c8
c4abe97
f770c42
a008757
e42d4b6
dfe0be1
b5dddc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" })); | ||
``` |
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/ |
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; | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in a world where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had that at some intermediate state in this PR, but it blew the module's bytecode size way over the limit (because the puppet module's bytecode had to be inlinted to the erc20 module) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh interesting! makes sense to go with this approach then I wonder if we could eventually get to a place where the install just takes in the CREATE2 address of the module and we expect it to exist on chain (or deployed via our deterministic deployer ahead of world deploy) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree that would be nice |
||
if (InstalledModules.get(PUPPET_MODULE_NAME, keccak256(new bytes(0))) == address(0)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side note/not blocking: since we're starting to use this more, might be nice to lift this into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
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(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we also need to install the ERC20Module here?I now see below that
registerERC20
installs the module