Skip to content

Commit fe89c1a

Browse files
authored
Merge pull request #1600 from qbzzt/250504-audit-custom-erc20
Audit of Custom SuperchainERC20 Tokens
2 parents b70699c + e8fcec7 commit fe89c1a

File tree

1 file changed

+125
-161
lines changed

1 file changed

+125
-161
lines changed

pages/interop/tutorials/custom-superchain-erc20.mdx

Lines changed: 125 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ categories:
1818
is_imported_content: 'false'
1919
---
2020

21-
import { Callout } from 'nextra/components'
22-
import { Steps } from 'nextra/components'
21+
import { Callout, Steps, Tabs } from 'nextra/components'
2322

2423
<Callout>
2524
The SuperchainERC20 standard is ready for production deployments.
@@ -35,168 +34,112 @@ This guide explains how to upgrade an ERC20 to a [`SuperchainERC20`](https://git
3534
To ensure fungibility across chains, `SuperchainERC20` assets must have the same contract address on all chains. This requirement abstracts away the complexity of cross-chain validation. Achieving this requires deterministic deployment methods. There are [many ways to do this](https://github.com/Arachnid/deterministic-deployment-proxy).
3635
Here we will use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit).
3736

38-
### What you'll do
39-
40-
* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy tokens with your custom code.
41-
42-
### What you'll learn
43-
44-
* How to deploy custom ERC20 tokens across multiple chains at the same address so that they can be bridged with the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract.
45-
46-
### How does this work?
47-
48-
To benefit from Superchain Interoperability, an ERC-20 token has to implement [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802). You can either use the SuperchainERC20 implementation, or write your own.
49-
50-
At a high level you will:
51-
52-
<Steps>
53-
54-
### Create a basic ERC20 contract
55-
56-
The following code implements a basic ERC20 contract:
57-
58-
```solidity
59-
// SPDX-License-Identifier: MIT
60-
pragma solidity ^0.8.20;
61-
62-
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
63-
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
64-
65-
contract MyERC20 is ERC20, Ownable {
66-
constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {}
67-
68-
function mint(address to, uint256 amount) public onlyOwner {
69-
_mint(to, amount);
70-
}
71-
}
72-
73-
```
74-
75-
### Add the new SuperchainERC20 interface
76-
77-
The first step is simply to implement [`IERC7802`](https://eips.ethereum.org/EIPS/eip-7802) and `IERC165`. Note that we've renamed the contract at this point:
78-
79-
```solidity
80-
// SPDX-License-Identifier: MIT
81-
pragma solidity ^0.8.20;
82-
83-
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
84-
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
85-
import {ERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
86-
import {IERC7802} from "@openzeppelin/contracts/token/ERC20/IERC7802.sol";
87-
88-
contract MySuperchainERC20 is ERC20, Ownable, IERC7802 {
89-
error NotSuperchainERC20Bridge();
90-
91-
constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {}
37+
<details>
38+
<summary>About this tutorial</summary>
9239

93-
function mint(address to, uint256 amount) public onlyOwner {
94-
_mint(to, amount);
95-
}
40+
**What you'll learn**
9641

42+
* How to deploy custom ERC20 tokens across multiple chains at the same address so that they can be bridged with the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract.
9743

98-
function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) {
99-
return _interfaceId == type(IERC7802).interfaceId ||
100-
_interfaceId == type(ERC20).interfaceId
101-
|| _interfaceId == type(ERC165).interfaceId;
102-
}
103-
}
44+
**Prerequisite technical knowledge**
10445

105-
```
46+
* Understanding of smart contract development
47+
* Familiarity with blockchain concepts
48+
* Familiarity with [standard SuperchainERC20 deployments](/interop/tutorials/deploy-superchain-erc20).
10649

107-
### Implement burn and mint functions
50+
**Development environment**
10851

109-
There are two functions we need to implement: `CrosschainMint` and `CrosschainBurn`. These two functions allow two chains to bridge a token by burning them on one chain and mint them on another. Read more about these functions in our [SuperchainERC20 docs](/interop/superchain-erc20).
52+
* Unix-like operating system (Linux, macOS, or WSL for Windows)
53+
* Git for version control
54+
</details>
11055

111-
Here's what our contract looks like once we've implemented the functions:
112-
113-
114-
```solidity
115-
// SPDX-License-Identifier: MIT
116-
pragma solidity ^0.8.20;
56+
### What you'll do
11757

118-
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
119-
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
120-
import {ERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
121-
import {IERC7802} from "@openzeppelin/contracts/token/ERC20/IERC7802.sol";
58+
* Use the [SuperchainERC20 starter kit](/app-developers/starter-kit) to deploy tokens with your custom code.
12259

123-
contract MySuperchainERC20 is ERC20, Ownable, IERC7802 {
124-
error NotSuperchainERC20Bridge();
60+
## Step by step
12561

126-
constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {}
62+
<Steps>
63+
### General setup
12764

128-
function mint(address to, uint256 amount) public onlyOwner {
129-
_mint(to, amount);
130-
}
65+
Follow the setup steps in the [SuperchainERC20 starter kit](/app-developers/starter-kit#setup), steps 1-4.
13166

132-
/// @notice Allows the SuperchainTokenBridge to mint tokens.
133-
/// @param _to Address to mint tokens to.
134-
/// @param _amount Amount of tokens to mint.
135-
function crosschainMint(address _to, uint256 _amount) external {
136-
if (msg.sender != 0x4200000000000000000000000000000000000028) revert NotSuperchainERC20Bridge();
67+
1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation).
13768

138-
_mint(_to, _amount);
69+
2. Run these commands:
13970

140-
emit CrosschainMint(_to, _amount, msg.sender);
141-
}
71+
```sh
72+
git clone https://github.com/ethereum-optimism/superchainerc20-starter.git
73+
cd superchainerc20-starter
74+
pnpm install
75+
pnpm init:env
76+
```
14277

143-
/// @notice Allows the SuperchainTokenBridge to burn tokens.
144-
/// @param _from Address to burn tokens from.
145-
/// @param _amount Amount of tokens to burn.
146-
function crosschainBurn(address _from, uint256 _amount) external {
147-
if (msg.sender != 0x4200000000000000000000000000000000000028) revert NotSuperchainERC20Bridge();
148-
149-
_burn(_from, _amount);
78+
3. Edit `packages/contracts/configs/deploy-config.toml`'s `[token]` section.
15079

151-
emit CrosschainBurn(_from, _amount, msg.sender);
152-
}
80+
| Parameter | Meaning | Example |
81+
| -------------- | ------------------------ | ------------------------ |
82+
| owner\_address | Owner of the token | Your address<sup>1</sup> |
83+
| name | Token name | Quick Transfer Token |
84+
| symbol | Token symbol | QTT |
85+
| decimals | Number of decimal places | 18 |
15386

154-
function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) {
155-
return _interfaceId == type(IERC7802).interfaceId ||
156-
_interfaceId == type(ERC20).interfaceId
157-
|| _interfaceId == type(ERC165).interfaceId;
158-
}
159-
}
160-
```
87+
(1) This should be an address you control (for which you know the private key), which has some ETH on the blockchains in question.
88+
In the case of Supersim, the default has 10k ETH and you can use it.
89+
For the devnets, [see here](/interop/tools/devnet#sending-eth-to-the-interop-devnets) to send ETH.
16190

162-
</Steps>
91+
4. If you change `owner_address`, you need to also set the private key.
92+
Edit `packages/contracts/.env` to set `DEPLOYER_PRIVATE_KEY` to the private key of an account that has ETH on both devnet blockchains.
16393

164-
For more details [see the explainer](/interop/superchain-erc20).
94+
```sh
95+
DEPLOYER_PRIVATE_KEY= <<<private key goes here>>>
96+
```
16597

166-
## Prerequisites
98+
### Blockchain-specific setup
16799

168-
Before starting this tutorial, ensure your development environment meets the following requirements:
100+
<Tabs items={['Supersim', 'Devnets']}>
101+
<Tabs.Tab>
102+
The [SuperchainERC20 Starter Kit](/app-developers/starter-kit) is already set up for [Supersim](/interop/tools/supersim).
103+
All you need to do is start it.
169104

170-
### Technical knowledge
105+
```sh
106+
./supersim --interop.autorelay
107+
```
108+
</Tabs.Tab>
171109

172-
* Understanding of smart contract development
173-
* Familiarity with blockchain concepts
174-
* Familiarity with [standard SuperchainERC20 deployments](./deploy-superchain-erc20).
110+
<Tabs.Tab>
111+
The [SuperchainERC20 Starter Kit](/app-developers/starter-kit) is already set up for [Supersim](/interop/tools/supersim).
112+
If you want to use it with a different set of blockchains, for example the [Devnets](/interop/tools/devnet), follow these steps.
175113

176-
### Development environment
114+
1. Edit `packages/contracts/foundry.toml` to add the RPC endpoints (add the bottom two rows).
177115

178-
* Unix-like operating system (Linux, macOS, or WSL for Windows)
179-
* Git for version control
180-
181-
### Required tools
182-
183-
The tutorial uses these primary tools:
116+
```toml
117+
[rpc_endpoints]
118+
op_chain_a = "http://127.0.0.1:9545"
119+
op_chain_b = "http://127.0.0.1:9546"
120+
devnet0 = "https://interop-alpha-0.optimism.io"
121+
devnet1 = "https://interop-alpha-1.optimism.io"
122+
```
184123

185-
* Foundry: For sending transactions to blockchains.
124+
You can import most RPC endpoints with this command, but it does not include the Interop devnet.
186125

187-
## Step by step explanation
126+
```sh
127+
pnpm contracts:update:rpcs
128+
```
188129

189-
<Steps>
190-
### Prepare for deployment
130+
2. Edit `packages/contracts/configs/deploy-config.toml`'s `[deploy-config]` section.
191131

192-
1. Follow the setup steps in the [SuperchainERC20 Starter Kit](/app-developers/starter-kit#setup).
193-
Don't start the development environment (step 5).
132+
| Parameter | Meaning | Example |
133+
| ---------------- | --------------------------------------------- | ---------------------- |
134+
| salt<sup>1</sup> | A unique identifier | Carthage |
135+
| chains | The chains to deploy the contract<sup>2</sup> | \["devnet0","devnet1"] |
194136

195-
2. Follow [the deployment preparations steps](./deploy-superchain-erc20#prepare-for-deployment) in the issuing new assets page.
196-
Don't deploy the contracts yet.
137+
(1) Make sure to specify a previously unused value for the salt, for example your address and a timestamp.
138+
This is necessary because if the same constructor code is used with the same salt when using the deployment script, it gets the same address, which is a problem if you want a fresh deployment.
197139

198-
**Note:** Make sure to specify a previously unused value for the salt, for example your address and a timestamp.
199-
This is necessary because if the same constructor code is used with the same salt when using the deployment script, it gets the same address, which is a problem if you want a fresh deployment.
140+
(2) These names must correspond to the chain names in the `[rpc_endpoints]` section of `foundry.toml` you updated in the previous step.
141+
</Tabs.Tab>
142+
</Tabs>
200143

201144
### Create the custom contract
202145

@@ -246,49 +189,70 @@ The tutorial uses these primary tools:
246189
pnpm contracts:deploy:token
247190
```
248191

249-
<details>
250-
<summary>Sanity check</summary>
192+
### Verify the installation
193+
194+
1. Set `TOKEN_ADDRESS` to the address where the token is deployed.
195+
You can also play with a previously created token, which is at address [`0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8) on the devnets.
196+
197+
```sh
198+
TOKEN_ADDRESS=<<< Your token address >>>
199+
```
251200

252-
1. Set `TOKEN_ADDRESS` to the address where the token is deployed.
253-
You can also play with the token I created, which is at address [`0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8).
201+
2. Source the `.env` file to get the private key and the address to which it corresponds.
254202

255-
```sh
256-
TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8
257-
```
203+
```sh
204+
. packages/contracts/.env
205+
USER_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY`
206+
```
258207

259-
2. Source the `.env` file to get the private key and the address to which it corresponds.
208+
3. Set variables for the RPC URLs.
260209

261-
```sh
262-
. packages/contracts/.env
263-
MY_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY`
264-
```
210+
<Tabs items={['Supersim', 'Devnets']}>
211+
<Tabs.Tab>
212+
Set these parameters for Supersim.
265213

266-
3. Set variables for the RPC URLs.
214+
```sh
215+
URL_CHAIN_A=http://127.0.0.1:9545
216+
URL_CHAIN_B=http://127.0.0.1:9546
217+
```
218+
</Tabs.Tab>
267219

268-
```sh
269-
RPC_DEV0=https://interop-alpha-0.optimism.io
270-
RPC_DEV1=https://interop-alpha-1.optimism.io
271-
```
220+
<Tabs.Tab>
221+
For Devnets, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters.
272222

273-
4. Get your current balance (it should be zero).
223+
```sh
224+
URL_CHAIN_A=https://interop-alpha-0.optimism.io
225+
URL_CHAIN_B=https://interop-alpha-1.optimism.io
226+
```
227+
</Tabs.Tab>
228+
</Tabs>
274229

275-
```sh
276-
cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
277-
```
230+
4. Get your current balance (it should be zero).
278231

279-
5. Call the faucet to get a token and check the balance again.
232+
```sh
233+
cast call --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei
234+
```
280235

281-
```sh
282-
cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "faucet()"
283-
cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
284-
```
285-
</details>
236+
5. Call the faucet to get a token and check the balance again.
237+
238+
```sh
239+
cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "faucet()"
240+
cast call --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei
241+
```
242+
243+
6. Repeat the same steps on Chain B.
244+
245+
```sh
246+
cast call --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei
247+
cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "faucet()"
248+
cast call --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei
249+
```
286250
</Steps>
287251

288252
For more details [see the explainer](/interop/superchain-erc20).
289253

290254
## Next steps
255+
291256
* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain.
292257
* If you'd like a guided walkthrough, check out our [tutorial video](https://x.com/i/status/1866095114374045969) instead.
293258
* Review the [Superchain Interop Explainer](/interop/explainer) for answers to common questions about interoperability.
294-

0 commit comments

Comments
 (0)