Skip to content

Commit

Permalink
feat: add crosschain erc20 interface
Browse files Browse the repository at this point in the history
  • Loading branch information
agusduha committed Oct 3, 2024
1 parent ae019b8 commit 99d0633
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 71 deletions.
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0xc6452d9aef6d76bdc789f3cddac6862658a481c619e6a2e7a74f6d61147f927b",
"initCodeHash": "0x964c826693c6633dc5eff6d4b059a30043775af46b06e42367aff91b904498da",
"sourceCodeHash": "0x4463e49c98ceb3327bd768579341d1e0863c8c3925d4b533fbc0f7951306261f"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
Expand All @@ -133,11 +133,11 @@
},
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0x9bc2e208774eb923894dbe391a5038a6189d7d36c202f4bf3e2c4dd332b0adf0"
"sourceCodeHash": "0x770764a897f76912ff5ad315ab7101f7777b8be47f21021d56cb9572b9efa458"
},
"src/L2/SuperchainERC20Bridge.sol": {
"initCodeHash": "0xea7eb314f96cd2520a58012ff7cc376c82c5a95612187ff6bb96ace4f095ebc4",
"sourceCodeHash": "0x83188d878ce0b2890a7f7f41d09a8807f94a126e0ea274f0dac8b93f77217d3b"
"initCodeHash": "0xf85225ea25a87ba670b6ce0172a4814fda712d1c8a174fd4e8ce72b1cebcc2a0",
"sourceCodeHash": "0x66b56c0ac0d49b6da84da01a318f43418ef486e5fb40ae0af487568fde8566fb"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"type": "uint256"
}
],
"name": "__superchainBurn",
"name": "__crosschainBurn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Expand All @@ -48,7 +48,7 @@
"type": "uint256"
}
],
"name": "__superchainMint",
"name": "__crosschainMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Expand Down Expand Up @@ -440,19 +440,6 @@
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand All @@ -469,7 +456,7 @@
"type": "uint256"
}
],
"name": "Mint",
"name": "CrosschainBurnt",
"type": "event"
},
{
Expand All @@ -488,7 +475,20 @@
"type": "uint256"
}
],
"name": "SuperchainBurnt",
"name": "CrosschainMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "Initialized",
"type": "event"
},
{
Expand All @@ -507,7 +507,7 @@
"type": "uint256"
}
],
"name": "SuperchainMinted",
"name": "Mint",
"type": "event"
},
{
Expand Down
13 changes: 7 additions & 6 deletions packages/contracts-bedrock/src/L2/SuperchainERC20.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
Expand All @@ -10,7 +11,7 @@ import { ERC20 } from "@solady/tokens/ERC20.sol";
/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token
/// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to
/// burn and mint tokens.
abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver {
abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISuperchainERC20Errors, ISemver {
/// @notice A modifier that only allows the SuperchainERC20Bridge to call
modifier onlySuperchainERC20Bridge() {
if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge();
Expand All @@ -26,18 +27,18 @@ abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver {
/// @notice Allows the SuperchainERC20Bridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge {
function __crosschainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge {
_mint(_to, _amount);

emit SuperchainMinted(_to, _amount);
emit CrosschainMinted(_to, _amount);
}

/// @notice Allows the SuperchainERC20Bridge to burn tokens.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge {
function __crosschainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge {
_burn(_from, _amount);

emit SuperchainBurnt(_from, _amount);
emit CrosschainBurnt(_from, _amount);
}
}
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge {
function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external {
if (_to == address(0)) revert ZeroAddress();

ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount);
ISuperchainERC20(_token).__crosschainBurn(msg.sender, _amount);

bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount));
IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message);
Expand All @@ -53,7 +53,7 @@ contract SuperchainERC20Bridge is ISuperchainERC20Bridge {

uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();

ISuperchainERC20(_token).__superchainMint(_to, _amount);
ISuperchainERC20(_token).__crosschainMint(_to, _amount);

emit RelayERC20(_token, _from, _to, _amount, source);
}
Expand Down
26 changes: 26 additions & 0 deletions packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title ICrosschainERC20
/// @notice This interface is a standard for crosschain ERC20 transfers.
interface ICrosschainERC20 {
/// @notice Emitted whenever tokens are minted by a crosschain transfer.
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event CrosschainMinted(address indexed account, uint256 amount);

/// @notice Emitted whenever tokens are burned by a crosschain transfer.
/// @param account Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event CrosschainBurnt(address indexed account, uint256 amount);

/// @notice Allows to mint tokens through a crosschain transfer.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function __crosschainMint(address _to, uint256 _amount) external;

/// @notice Allows to burn tokens through a crosschain transfer.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function __crosschainBurn(address _from, uint256 _amount) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.0;

// Interfaces
import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";

/// @title IOptimismSuperchainERC20Errors
Expand Down
23 changes: 2 additions & 21 deletions packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

// Interfaces
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";

/// @title ISuperchainERC20Errors
Expand All @@ -13,27 +14,7 @@ interface ISuperchainERC20Errors {

/// @title ISuperchainERC20Extension
/// @notice This interface is available on the SuperchainERC20 contract.
interface ISuperchainERC20Extension is ISuperchainERC20Errors {
/// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge.
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event SuperchainMinted(address indexed account, uint256 amount);

/// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge.
/// @param account Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event SuperchainBurnt(address indexed account, uint256 amount);

/// @notice Allows the SuperchainERC20Bridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function __superchainMint(address _to, uint256 _amount) external;

/// @notice Allows the SuperchainERC20Bridge to burn tokens.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function __superchainBurn(address _from, uint256 _amount) external;
}
interface ISuperchainERC20Extension is ICrosschainERC20, ISuperchainERC20Errors { }

/// @title ISuperchainERC20
/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface.
Expand Down
31 changes: 16 additions & 15 deletions packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";
import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol";

// Target contract
import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol";
import { SuperchainERC20Implementation_MockContract } from "test/mocks/SuperchainERC20Implementation.sol";

Expand All @@ -41,7 +42,7 @@ contract SuperchainERC20Test is Test {
}

/// @notice Tests the `mint` function reverts when the caller is not the bridge.
function testFuzz___superchainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public {
function testFuzz___crosschainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public {
// Ensure the caller is not the bridge
vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE);

Expand All @@ -50,11 +51,11 @@ contract SuperchainERC20Test is Test {

// Call the `mint` function with the non-bridge caller
vm.prank(_caller);
superchainERC20.__superchainMint(_to, _amount);
superchainERC20.__crosschainMint(_to, _amount);
}

/// @notice Tests the `mint` succeeds and emits the `Mint` event.
function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public {
function testFuzz___crosschainMint_succeeds(address _to, uint256 _amount) public {
// Ensure `_to` is not the zero address
vm.assume(_to != ZERO_ADDRESS);

Expand All @@ -66,21 +67,21 @@ contract SuperchainERC20Test is Test {
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount);

// Look for the emit of the `SuperchainMinted` event
// Look for the emit of the `CrosschainMinted` event
vm.expectEmit(address(superchainERC20));
emit ISuperchainERC20Extension.SuperchainMinted(_to, _amount);
emit ICrosschainERC20.CrosschainMinted(_to, _amount);

// Call the `mint` function with the bridge caller
vm.prank(SUPERCHAIN_ERC20_BRIDGE);
superchainERC20.__superchainMint(_to, _amount);
superchainERC20.__crosschainMint(_to, _amount);

// Check the total supply and balance of `_to` after the mint were updated correctly
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount);
assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount);
}

/// @notice Tests the `burn` function reverts when the caller is not the bridge.
function testFuzz___superchainBurn_callerNotBridge_reverts(
function testFuzz___crosschainBurn_callerNotBridge_reverts(
address _caller,
address _from,
uint256 _amount
Expand All @@ -95,17 +96,17 @@ contract SuperchainERC20Test is Test {

// Call the `burn` function with the non-bridge caller
vm.prank(_caller);
superchainERC20.__superchainBurn(_from, _amount);
superchainERC20.__crosschainBurn(_from, _amount);
}

/// @notice Tests the `burn` burns the amount and emits the `SuperchainBurnt` event.
function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public {
/// @notice Tests the `burn` burns the amount and emits the `CrosschainBurnt` event.
function testFuzz___crosschainBurn_succeeds(address _from, uint256 _amount) public {
// Ensure `_from` is not the zero address
vm.assume(_from != ZERO_ADDRESS);

// Mint some tokens to `_from` so then they can be burned
vm.prank(SUPERCHAIN_ERC20_BRIDGE);
superchainERC20.__superchainMint(_from, _amount);
superchainERC20.__crosschainMint(_from, _amount);

// Get the total supply and balance of `_from` before the burn to compare later on the assertions
uint256 _totalSupplyBefore = superchainERC20.totalSupply();
Expand All @@ -115,13 +116,13 @@ contract SuperchainERC20Test is Test {
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount);

// Look for the emit of the `SuperchainBurnt` event
// Look for the emit of the `CrosschainBurnt` event
vm.expectEmit(address(superchainERC20));
emit ISuperchainERC20Extension.SuperchainBurnt(_from, _amount);
emit ICrosschainERC20.CrosschainBurnt(_from, _amount);

// Call the `burn` function with the bridge caller
vm.prank(SUPERCHAIN_ERC20_BRIDGE);
superchainERC20.__superchainBurn(_from, _amount);
superchainERC20.__crosschainBurn(_from, _amount);

// Check the total supply and balance of `_from` after the burn were updated correctly
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ contract SuperchainERC20BridgeTest is Bridge_Initializer {

// Mint some tokens to the sender so then they can be sent
vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE);
superchainERC20.__superchainMint(_sender, _amount);
superchainERC20.__crosschainMint(_sender, _amount);

// Get the total supply and balance of `_sender` before the send to compare later on the assertions
uint256 _totalSupplyBefore = superchainERC20.totalSupply();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
bytes32 salt = MESSENGER.superTokenInitDeploySalts(token);
amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply());
vm.prank(sender);
try OptimismSuperchainERC20(token).__superchainMint(to, amount) {
try OptimismSuperchainERC20(token).__crosschainMint(to, amount) {
compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue + amount);
Expand All @@ -33,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
bytes32 salt = MESSENGER.superTokenInitDeploySalts(token);
uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender);
vm.prank(sender);
try OptimismSuperchainERC20(token).__superchainBurn(from, amount) {
try OptimismSuperchainERC20(token).__crosschainBurn(from, amount) {
compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue - amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors {
index = bound(index, 0, allSuperTokens.length - 1);
address addr = allSuperTokens[index];
vm.prank(BRIDGE);
OptimismSuperchainERC20(addr).__superchainMint(currentActor(), amount);
OptimismSuperchainERC20(addr).__crosschainMint(currentActor(), amount);
// currentValue will be zero if key is not present
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr));
ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount);
Expand Down

0 comments on commit 99d0633

Please sign in to comment.