Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add crosschain erc20 interface #80

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the same as inheriting from ISuperchainERC20Exension directly?

/// @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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @notice This interface is a standard for crosschain ERC20 transfers.
/// @notice Defines the interface for crosschain ERC20 transfers.

interface ICrosschainERC20 {
/// @notice Emitted whenever tokens are minted by a crosschain transfer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer active voice:

Suggested change
/// @notice Emitted whenever tokens are minted by a crosschain transfer.
/// @notice Emitted when a crosschain transfer mints tokens.

same for the burn event

/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event CrosschainMinted(address indexed account, uint256 amount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is account pattern matching a previous pattern from them? does it make sense to call it to like the function argument name? or recipient?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they have them like this in OptimismMintable but i would prefer to use to and from in both events


/// @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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatives:

  1. Mints tokens due after a crosschain transfer.
  2. Mints tokens via 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatives:

  1. Burns tokens to initiate a crosschain transfer.
  2. Burns tokens via 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