From c5a76eddf0c4fa444676a647f03dd672553f589d Mon Sep 17 00:00:00 2001 From: V Date: Thu, 28 Nov 2024 09:40:01 -0300 Subject: [PATCH] docs(world-module-erc20): replace erc20 docs (#3383) --- docs/pages/world/modules/erc20.mdx | 225 ++++++----------------------- 1 file changed, 42 insertions(+), 183 deletions(-) diff --git a/docs/pages/world/modules/erc20.mdx b/docs/pages/world/modules/erc20.mdx index 1bd906b91c..78c6f7569e 100644 --- a/docs/pages/world/modules/erc20.mdx +++ b/docs/pages/world/modules/erc20.mdx @@ -9,32 +9,22 @@ This module is unaudited and may change in the future. -The [`erc20-puppet`](https://github.com/latticexyz/mud/tree/main/packages/world-modules/src/modules/erc20-puppet) module lets you create [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens as part of a MUD `World`. +The [`erc20` module](https://github.com/latticexyz/mud/tree/main/packages/world-module-erc20/) lets you create [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens as part of a MUD `World`. The advantage of doing this, rather than creating a separate [ERC-20 contract](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20) and merely controlling it from MUD, is that all the information is in MUD tables and is immediately available in the client. -## Deployment +The ERC20Module receives the namespace, name and symbol of the token as parameters, and deploys the new token. Currently it installs a [default ERC20](https://github.com/latticexyz/mud/tree/main/packages/world-module-erc20/src/examples/ERC20WithWorld.sol) with the following features: -The easiest way to deploy this module is to edit `mud.config.ts`. -This is a modified version of the [vanilla](/templates/typescript/contracts) template. +- 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. -Note that before you use this file you need to run `pnpm add viem` (see explanation below). +## Installation - +The simplest way to install this module and register a new ERC20 token in your world is to import the `defineERC20Module` helper and use it to add the module's declaration to your MUD config: -```typescript filename="mud.config.ts" showLineNumbers copy {2-13,25-41} +```typescript filename="mud.config.ts" import { defineWorld } from "@latticexyz/world"; -import { encodeAbiParameters, stringToHex } from "viem"; - -const erc20ModuleArgs = encodeAbiParameters( - [ - { type: "bytes14" }, - { - type: "tuple", - components: [{ type: "uint8" }, { type: "string" }, { type: "string" }], - }, - ], - [stringToHex("MyToken", { size: 14 }), [18, "Worthless Token", "WT"]], -); +import { defineERC20Module } from "@latticexyz/world-module-erc20/internal"; export default defineWorld({ namespace: "app", @@ -47,133 +37,19 @@ export default defineWorld({ }, }, modules: [ - { - artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json", - root: false, - args: [], - }, - { - artifactPath: "@latticexyz/world-modules/out/ERC20Module.sol/ERC20Module.json", - root: false, - args: [ - { - type: "bytes", - value: erc20ModuleArgs, - }, - ], - }, + defineERC20Module({ + // The new namespace the module will register + namespace: "erc20Namespace", + // The metadata of the ERC20 token that will be deployed by the module + name: "MyToken", + symbol: "MTK", + }), ], }); ``` - - -
- -Explanation - -```typescript -import { encodeAbiParameters, stringToHex } from "viem"; -``` - -In simple cases it is enough to use the config parser to specify the module arguments. -However, the ERC-20 module requires a `struct` as [one of the arguments](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol#L34). -We use [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters.html) to encode the `struct` data. -The [`stringToHex`](https://viem.sh/docs/utilities/toHex.html#stringtohex) function is used to specify the namespace the token uses. - -This is the reason we need to issue `pnpm install viem` in `packages/contracts` to be able to use the library here. - -```typescript -const erc20ModuleArgs = encodeAbiParameters( -``` - -You can see the arguments for the ERC-20 module [here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc20-puppet/ERC20Module.sol#L34). -There are two arguments: - -- A 14-byte identifier for the namespace. -- An `ERC20MetadataData` for the ERC-20 parameters, [defined here](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol#L19-L23). - -However, the arguments for a module are [ABI encoded](https://docs.soliditylang.org/en/develop/abi-spec.html) to a single value of type `bytes`. -So we use `encodeAbiParameters` from the viem library to create this argument. -The first parameter of this function is a list of argument types. - -```typescript - [ - { type: "bytes14" }, -``` - -The first parameter is simple, a 14 byte value for the namespace. - -```typescript - { - type: "tuple", - components: [{ type: "uint8" }, { type: "string" }, { type: "string" }], - }, -``` - -The second value is more complicated, it's a struct, or as it is called in ABI, a tuple. -The first field is the number of digits after the decimal point when displaying the token. -The second field is the token's full name, and the third a short symbol for it. - -```typescript - [ - stringToHex("MyToken", { size: 14 }), -``` - -The second `encodeAbiParameters` parameter is a list of the values, of the types declared in the first list. - -The first parameter for the module is `bytes14`, the namespace of the ERC-20 token. -We use [`stringToHex`](https://viem.sh/docs/utilities/toHex.html#stringtohex) to convert it from the text form that is easy for us to use, to the hexadecimal number that Viem expects for `bytes14` parameter. - -```typescript - [18, "Worthless Token", "WT"]], - ], -); -``` - -The second parameter for the module is the [`ERC20MetadataData`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc20-puppet/tables/ERC20Metadata.sol#L19-L23) structure. - -```typesceript - modules: [ - { - artifactPath: "@latticexyz/world-modules/out/PuppetModule.sol/PuppetModule.json", - root: false, - args: [], - }, -``` - -A module declaration requires three parameters: - -- `artifactPath`, a link to the compiled JSON file for the module. -- `root`, whether to install the module with [root namespace permissions](/world/systems#root-systems) or not. -- `args` the module arguments. - -Here we install [the `puppet` module](https://github.com/latticexyz/mud/tree/main/packages/world-modules/src/modules/puppet). -We need this module because a `System` is supposed to be stateless, and easily upgradeable to a contract in a different address. -However, both the [ERC-20 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) and the [ERC-721 standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) require the token contract to emit events. -The solution is to put the `System` in one contract and have another contract, the puppet, which receives requests and emits events according to the ERC. - -```typescript - { - artifactPath: "@latticexyz/world-modules/out/ERC20Module.sol/ERC20Module.json", - root: false, - args: [ - { - type: "bytes", -``` - -The data type for this parameter is `bytes`, because it is treated as opaque bytes by the `World` and only gets parsed by the module after it is transferred. - -```typescript - value: erc20ModuleArgs, - }, - ], - }, -``` - -The module arguments, stored in `erc20ModuleArgs`. - -
+This will deploy the token and register it under the provided namespace. Note that the namespace must not exist beforehand, as the module will create it upon installation. +The ownership of the new namespace will be transferred to the deployer after installation. ## Usage @@ -182,25 +58,24 @@ For example, run this script. -```solidity filename="ManageERC20.s.sol" copy showLineNumbers {16,35-39,45,50-64} +```solidity filename="ManageERC20.s.sol" copy showLineNumbers {16,34-38,43,48-56} +import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { RESOURCE_TABLE } from "@latticexyz/store/src/storeResourceTypes.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; -import { ERC20Registry } from "@latticexyz/world-modules/src/codegen/index.sol"; -import { IERC20Mintable } from "@latticexyz/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol"; +import { ERC20Registry } from "@latticexyz/world-module-erc20/src/codegen/index.sol"; +import { ERC20WithWorld as ERC20 } from "@latticexyz/world-module-erc20/src/examples/ERC20WithWorld.sol"; import { IWorld } from "../src/codegen/world/IWorld.sol"; contract ManageERC20 is Script { - function reportBalances(IERC20Mintable erc20, address myAddress) internal view { - address goodGuy = address(0x600D); - address badGuy = address(0x0BAD); + function reportBalances(ERC20 erc20, address myAddress) internal view { + address alice = address(0x600D); console.log(" My balance:", erc20.balanceOf(myAddress)); - console.log("Goodguy balance:", erc20.balanceOf(goodGuy)); - console.log(" Badguy balance:", erc20.balanceOf(badGuy)); + console.log("Alice's balance:", erc20.balanceOf(alice)); console.log("--------------"); } @@ -218,34 +93,27 @@ contract ManageERC20 is Script { vm.startBroadcast(deployerPrivateKey); // Get the ERC-20 token address - ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("MyToken")); - ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-puppet", "ERC20Registry"); + ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace")); + ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY"); address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource); console.log("Token address", tokenAddress); - address goodGuy = address(0x600D); - address badGuy = address(0x0BAD); + address alice = address(0x600D); // Use the token - IERC20Mintable erc20 = IERC20Mintable(tokenAddress); + ERC20 erc20 = ERC20(tokenAddress); console.log("Initial state"); reportBalances(erc20, myAddress); // Mint some tokens - console.log("Minting for myself and Badguy"); + console.log("Minting for myself"); erc20.mint(myAddress, 1000); - erc20.mint(badGuy, 500); reportBalances(erc20, myAddress); // Transfer tokens - console.log("Transfering to Goodguy"); - erc20.transfer(goodGuy, 750); - reportBalances(erc20, myAddress); - - // Burn tokens - console.log("Burning badGuy's tokens"); - erc20.burn(badGuy, 500); + console.log("Transfering to Alice"); + erc20.transfer(alice, 750); reportBalances(erc20, myAddress); vm.stopBroadcast(); @@ -267,48 +135,39 @@ contract ManageERC20 is Script { ```solidity // Get the ERC-20 token address - ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("MyToken")); - ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-puppet", "ERC20Registry"); + ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("erc20Namespace")); + ResourceId erc20RegistryResource = WorldResourceIdLib.encode(RESOURCE_TABLE, "erc20-module", "ERC20_REGISTRY"); address tokenAddress = ERC20Registry.getTokenAddress(erc20RegistryResource, namespaceResource); console.log("Token address", tokenAddress); ``` -This is the process to get the address of our token contract (the puppet). -First, we get the [`resourceId` values](/world/resource-ids) for the `erc20-puppet__ERC20Registry` table and the namespace we are interested in (each namespace can only have one ERC-20 token). +This is the process to get the address of our token contract. +First, we get the [`resourceId` values](/world/resource-ids) for the `erc20-module__ERC20Registry` table and the namespace we are interested in (each namespace can only have one ERC-20 token). Then we use that table to get the token address. ```solidity // Use the token - IERC20Mintable erc20 = IERC20Mintable(tokenAddress); + ERC20 erc20 = ERC20(tokenAddress); ``` -Create an [`IERC20Mintable`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/erc20-puppet/IERC20Mintable.sol) for the token. +Cast the token address to an `ERC20` contract so we can call its methods. ```solidity - console.log("Minting for myself and Badguy"); + console.log("Minting for myself"); erc20.mint(myAddress, 1000); - erc20.mint(badGuy, 500); reportBalances(erc20, myAddress); ``` -Mint tokens for two addresses. +Mint tokens for your address. Note that only the owner of the name space is authorized to mint tokens. ```solidity - console.log("Transfering to Goodguy"); - erc20.transfer(goodGuy, 750); + console.log("Transfering to Alice"); + erc20.transfer(alice, 750); reportBalances(erc20, myAddress); ``` Transfer a token. We can only transfer tokens we own, or that we have approval to transfer from the current owner. -```solidity - console.log("Burning badGuy's tokens"); - erc20.burn(badGuy, 500); - reportBalances(erc20, myAddress); -``` - -Destroy some tokens. -