Skip to content

Commit

Permalink
feat: Add example of how to make existing ERC20 compatible with super…
Browse files Browse the repository at this point in the history
…chain interop
  • Loading branch information
tremarkley committed Nov 22, 2024
1 parent 0d36a50 commit eb6a4e7
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,83 @@ Verify that the balance for the recipient of the `L2NativeSuperchainERC20` on ch
cast balance --erc20 <deployed-token-address> <recipient-address> --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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit eb6a4e7

Please sign in to comment.