-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(world-module-erc20): new ERC20 World Module (#3300)
Co-authored-by: alvarius <[email protected]>
- Loading branch information
Showing
43 changed files
with
4,142 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
"@latticexyz/world-module-erc20": patch | ||
--- | ||
|
||
The new ERC20 World Module provides a simpler alternative to the ERC20 Puppet Module, while also being structured in a more extendable way so users can create tokens with custom functionality. | ||
|
||
To install this module, you can import and define the module configuration from the NPM package: | ||
|
||
```typescript | ||
import { defineERC20Config } from "@latticexyz/world-module-erc20"; | ||
|
||
// Add the output of this function to your World's modules | ||
const config = defineERC20Config({ namespace: "erc20Namespace", name: "MyToken", symbol: "MTK" }); | ||
``` | ||
|
||
For detailed installation instructions, please check out the [`@latticexyz/world-module-erc20` README.md](https://github.com/latticexyz/mud/blob/main/packages/world-module-erc20/README.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cache | ||
out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "solhint:recommended", | ||
"rules": { | ||
"compiler-version": ["error", ">=0.8.0"], | ||
"avoid-low-level-calls": "off", | ||
"func-visibility": ["warn", { "ignoreConstructors": true }] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# ERC20 World Module | ||
|
||
> :warning: **Important note: this module has not been audited yet, so any production use is discouraged for now.** | ||
## 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. | ||
|
||
- `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. | ||
|
||
- `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()`. | ||
|
||
### Example 1: Using the contract's storage | ||
|
||
By using `WithStore(address(this))` as the first contract that the implementation inherits from, it allows all the other contracts in the inheritance list to use the contract's storage as a `Store`. | ||
|
||
```solidity | ||
contract ERC20WithInternalStore is WithStore(address(this)), MUDERC20, ERC20Pausable, ERC20Burnable, Ownable { | ||
constructor() MUDERC20("MyERC20", "MTK") Ownable(_msgSender()) {} | ||
function mint() public onlyOwner { | ||
_mint(to, value); | ||
} | ||
function pause() public onlyOwner { | ||
_pause(); | ||
} | ||
function unpause() public onlyOwner { | ||
_unpause(); | ||
} | ||
// The following functions are overrides required by Solidity. | ||
function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { | ||
super._update(from, to, value); | ||
} | ||
} | ||
``` | ||
|
||
### Example 2: Using a World as an external Store and registering a new Namespace | ||
|
||
The `WithWorld` contract internally points the `StoreSwitch` to the provided World and attempts to register the provided namespace. It allows the other contracts in the inheritance list to use the external World as a `Store`, using the provided namespace for all operations. Additionally, all functions that use the `onlyNamespace` modifier can only be called by addresses that have access to the namespace. | ||
|
||
```solidity | ||
contract ERC20WithWorld is WithWorld, MUDERC20, ERC20Pausable, ERC20Burnable { | ||
constructor( | ||
IBaseWorld world, | ||
bytes14 namespace, | ||
string memory name, | ||
string memory symbol | ||
) WithWorld(world, namespace) MUDERC20(name, symbol) { | ||
// transfer namespace ownership to the creator | ||
world.transferOwnership(getNamespaceId(), _msgSender()); | ||
} | ||
function mint(address to, uint256 value) public onlyNamespace { | ||
_mint(to, value); | ||
} | ||
function pause() public onlyNamespace { | ||
_pause(); | ||
} | ||
function unpause() public onlyNamespace { | ||
_unpause(); | ||
} | ||
// The following functions are overrides required by Solidity. | ||
function _update(address from, address to, uint256 value) internal override(MUDERC20, ERC20Pausable) { | ||
super._update(from, to, value); | ||
} | ||
} | ||
``` | ||
|
||
# Module usage | ||
|
||
The ERC20Module receives the namespace, name and symbol of the token as parameters, and deploys the new token. Currently it installs a default ERC20 (`examples/ERC20WithWorld.sol`) with the following features: | ||
|
||
- ERC20Burnable: Allows users to burn their tokens (or the ones approved to them) using the `burn` and `burnFrom` function. | ||
- ERC20Pausable: Supports pausing and unpausing token operations. This is combined with the `pause` and `unpause` public functions that can be called by addresses with access to the token's namespace. | ||
- Minting: Addresses with namespace access can call the `mint` function to mint tokens to any address. | ||
|
||
## Installation | ||
|
||
In your MUD config: | ||
|
||
```typescript | ||
import { defineWorld } from "@latticexyz/world"; | ||
import { defineERC20Config } from "@latticexyz/world-module-erc20"; | ||
|
||
export default defineWorld({ | ||
namespace: "app", | ||
tables: { | ||
Counter: { | ||
schema: { | ||
value: "uint32", | ||
}, | ||
key: [], | ||
}, | ||
}, | ||
modules: [ | ||
defineERC20Config({ | ||
namespace: "erc20Namespace", | ||
name: "MyToken", | ||
symbol: "MTK", | ||
}), | ||
], | ||
}); | ||
``` | ||
|
||
This will deploy the token and register the provided namespace. | ||
|
||
In order to get the token's address in a script or system: | ||
|
||
```solidity | ||
// Table Id of the ERC20Registry, under the `erc20-module` namespace | ||
ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY"); | ||
// Namespace where the token was installed | ||
ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace")); | ||
// Get the ERC-20 token address | ||
address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
] |
Oops, something went wrong.