From eb6a4e7b7a8ea9c2d9c5fb8bbd441dfc9a51858a Mon Sep 17 00:00:00 2001 From: tre Date: Fri, 22 Nov 2024 15:47:11 -0800 Subject: [PATCH] feat: Add example of how to make existing ERC20 compatible with superchain interop --- README.md | 77 ++++++++++++++++++ .../L2NativeInteroperableGovernanceToken.sol | 79 +++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 packages/contracts/src/examples/L2NativeInteroperableGovernanceToken.sol diff --git a/README.md b/README.md index 19db8567..e35bdaf1 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,83 @@ Verify that the balance for the recipient of the `L2NativeSuperchainERC20` on ch cast balance --erc20 --rpc-url http://127.0.0.1:9546 ``` +## Updating an ERC20 contract to be interoperable + +This section gives instructions on what changes need to be made to an existing ERC20 contract in order to make it interoperable in the Superchain. + +For this example, we will be making the [GovernanceToken](https://github.com/ethereum-optimism/optimism/blob/80465cd6c428d13166fce351741492f354621643/packages/contracts-bedrock/src/governance/GovernanceToken.sol) `ERC20` interoperable. To do this we just need to implement the [IERC7802](https://ethereum-magicians.org/t/erc-7802-crosschain-token-interface/21508/2) interface. + +**1. Add the IERC7802 interface to the contract** + +```solidity +import { IERC7802, IERC165 } from "@contracts-bedrock/L2/interfaces/IERC7802.sol"; + +contract GovernanceToken is IERC7802, ERC20Burnable, ERC20Votes, Ownable +``` + +**2. Add the `crosschainMint` function to the contract** + +The `crosschainMint` function defines the logic for minting the token when it is transferred to the destination chain. + +In this example, only the `SuperchainTokenBridge` contract will have permissions to mint the token during a crosschain transfer. If you have your own custom bridge you would like to use, you can use that here as well. + +```solidity +/// @notice Allows the SuperchainTokenBridge to mint tokens. +/// @param _to Address to mint tokens to. +/// @param _amount Amount of tokens to mint. +function crosschainMint(address _to, uint256 _amount) external { + // Only the `SuperchainTokenBridge` has permissions to mint tokens during crosschain transfers. + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + // Mint tokens to the `_to` account's balance. + _mint(_to, _amount); + + // Emit the CrosschainMint event included on IERC7802 for tracking token mints associated with cross chain transfers. + emit CrosschainMint(_to, _amount, msg.sender); +} +``` + +**3. Add the `crosschainBurn` function to the contract** +The `crosschainBurn` function defines the logic for burning the token on the source chain prior to it being minted on the destination chain. + +In this example, only the `SuperchainTokenBridge` contract will have permissions to burn the token during a crosschain transfer. If you have your own custom bridge you would like to use, you can use that here as well. + +```solidity +/// @notice Allows the SuperchainTokenBridge to burn tokens. +/// @param _from Address to burn tokens from. +/// @param _amount Amount of tokens to burn. +function crosschainBurn(address _from, uint256 _amount) external { + // Only the `SuperchainTokenBridge` has permissions to burn tokens during crosschain transfers. + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + // Burn the tokens from the `_from` account's balance. + _burn(_from, _amount); + + // Emit the CrosschainBurn event included on IERC7802 for tracking token burns associated with cross chain transfers. + emit CrosschainBurn(_from, _amount, msg.sender); +} +``` + +**4. Add the `supportsInterface` function to the contract** +`IERC7802` is also an implementation of [IERC165](https://eips.ethereum.org/EIPS/eip-165), so you must add the `supportsInterface` function in order to specify which interfaces your token supports. + +In this example our token support `IERC7802`, `IERC20`, and `IERC165` + +```solidity +/// @notice Query if a contract implements an interface +/// @param interfaceID The interface identifier, as specified in ERC-165 +/// @dev Interface identification is specified in ERC-165. This function +/// uses less than 30,000 gas. +/// @return `true` if the contract implements `interfaceID` and +/// `interfaceID` is not 0xffffffff, `false` otherwise +function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId + || _interfaceId == type(IERC165).interfaceId; +} +``` + +After following these four steps your token is ready to be interoperable! The full example contract is available [here](packages/contracts/src/examples/L2NativeInteroperableGovernanceToken.sol) + ## 🤝 Contributing Contributions are encouraged, but please open an issue before making any major changes to ensure your changes will be accepted. diff --git a/packages/contracts/src/examples/L2NativeInteroperableGovernanceToken.sol b/packages/contracts/src/examples/L2NativeInteroperableGovernanceToken.sol new file mode 100644 index 00000000..0479dae8 --- /dev/null +++ b/packages/contracts/src/examples/L2NativeInteroperableGovernanceToken.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import { ERC20Votes, ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC7802, IERC165 } from "@contracts-bedrock/L2/interfaces/IERC7802.sol"; +import { Predeploys } from "@contracts-bedrock/libraries/Predeploys.sol"; +import { Unauthorized } from "@contracts-bedrock/libraries/errors/CommonErrors.sol"; + +contract GovernanceToken is IERC7802, ERC20Burnable, ERC20Votes, Ownable { + /// @notice Constructs the GovernanceToken contract. + constructor() ERC20("Optimism", "OP") ERC20Permit("Optimism") { } + + /// @notice Allows the owner to mint tokens. + /// @param _account The account receiving minted tokens. + /// @param _amount The amount of tokens to mint. + function mint(address _account, uint256 _amount) public onlyOwner { + _mint(_account, _amount); + } + + /// @notice Callback called after a token transfer. + /// @param from The account sending tokens. + /// @param to The account receiving tokens. + /// @param amount The amount of tokens being transfered. + function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._afterTokenTransfer(from, to, amount); + } + + /// @notice Internal mint function. + /// @param to The account receiving minted tokens. + /// @param amount The amount of tokens to mint. + function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._mint(to, amount); + } + + /// @notice Internal burn function. + /// @param account The account that tokens will be burned from. + /// @param amount The amount of tokens that will be burned. + function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) { + super._burn(account, amount); + } + + /// @notice Allows the SuperchainTokenBridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function crosschainMint(address _to, uint256 _amount) external { + // Only the `SuperchainTokenBridge` has permissions to mint tokens during crosschain transfers. + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + // Mint tokens to the `_to` account's balance. + _mint(_to, _amount); + + // Emit the CrosschainMint event included on IERC7802 for tracking token mints associated with cross chain transfers. + emit CrosschainMint(_to, _amount, msg.sender); + } + + /// @notice Allows the SuperchainTokenBridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function crosschainBurn(address _from, uint256 _amount) external { + // Only the `SuperchainTokenBridge` has permissions to burn tokens during crosschain transfers. + if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized(); + + // Burn the tokens from the `_from` account's balance. + _burn(_from, _amount); + + // Emit the CrosschainBurn event included on IERC7802 for tracking token burns associated with cross chain transfers. + emit CrosschainBurn(_from, _amount, msg.sender); + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId + || _interfaceId == type(IERC165).interfaceId; + } +}