Skip to content

Commit

Permalink
feat: add superchain erc20 bridge (#61)
Browse files Browse the repository at this point in the history
* feat: add superchain erc20 bridge

* fix: interfaces and versions
  • Loading branch information
agusduha authored Sep 25, 2024
1 parent 0fee34b commit ae024a5
Show file tree
Hide file tree
Showing 14 changed files with 317 additions and 22 deletions.
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564362)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92970)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155607)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610)
2 changes: 2 additions & 0 deletions packages/contracts-bedrock/scripts/Artifacts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ abstract contract Artifacts {
return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
} else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) {
return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON);
} else if (digest == keccak256(bytes("SuperchainERC20Bridge"))) {
return payable(Predeploys.SUPERCHAIN_ERC20_BRIDGE);
}
return payable(address(0));
}
Expand Down
7 changes: 7 additions & 0 deletions packages/contracts-bedrock/scripts/L2Genesis.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ contract L2Genesis is Deployer {
setETHLiquidity(); // 25
setOptimismSuperchainERC20Factory(); // 26
setOptimismSuperchainERC20Beacon(); // 27
setSuperchainERC20Bridge(); // 28
}
}

Expand Down Expand Up @@ -555,6 +556,12 @@ contract L2Genesis is Deployer {
vm.resetNonce(address(beacon));
}

/// @notice This predeploy is following the safety invariant #1.
/// This contract has no initializer.
function setSuperchainERC20Bridge() internal {
_setImplementationCode(Predeploys.SUPERCHAIN_ERC20_BRIDGE);
}

/// @notice Sets all the preinstalls.
function setPreinstalls() public {
address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls"))));
Expand Down
8 changes: 6 additions & 2 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@
"sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84"
},
"src/L2/L2StandardBridgeInterop.sol": {
"initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef",
"sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994"
"initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6",
"sourceCodeHash": "0xc52d7a8b5d747167870d9048710e960e925d39650e3bd3fbc201b9b4f771e052"
},
"src/L2/L2ToL1MessagePasser.sol": {
"initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d",
Expand All @@ -131,6 +131,10 @@
"initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74",
"sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a"
},
"src/L2/SuperchainERC20Bridge.sol": {
"initCodeHash": "0x802574bf35587e9a8dc2416e91b9fd1411c75d219545b8b55d25a75452459b10",
"sourceCodeHash": "0xb11ce94fd6165d8ca86eebafc7235e0875380d1a5d4e8b267ff0c6477083b21c"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0xd8766c7ab41d34d935febf5b48289f947804634bde38f8e346075b9f2d867275",
"sourceCodeHash": "0x6c1691c0fb5c86f1fd67e23495725c2cd86567556602e8cc0f28104ad6114bf4"
Expand Down
155 changes: 155 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "address",
"name": "_from",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "relayERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_chainId",
"type": "uint256"
}
],
"name": "sendERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "source",
"type": "uint256"
}
],
"name": "RelayERC20",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "destination",
"type": "uint256"
}
],
"name": "SendERC20",
"type": "event"
},
{
"inputs": [],
"name": "CallerNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
17 changes: 5 additions & 12 deletions packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol";
import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol";

/// @notice Thrown when the decimals of the tokens are not the same.
error InvalidDecimals();
Expand All @@ -24,14 +25,6 @@ error InvalidSuperchainERC20Address();
/// @notice Thrown when the remote addresses of the tokens are not the same.
error InvalidTokenPair();

/// TODO: Define a better naming convention for this interface.
/// @notice Interface for minting and burning tokens in the L2StandardBridge.
/// Used for StandardL2ERC20, OptimismMintableERC20 and OptimismSuperchainERC20.
interface MintableAndBurnable is IERC20 {
function mint(address, uint256) external;
function burn(address, uint256) external;
}

/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000010
/// @title L2StandardBridgeInterop
Expand All @@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge {
event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount);

/// @notice Semantic version.
/// @custom:semver +interop
/// @custom:semver +interop-beta.1
function version() public pure override returns (string memory) {
return string.concat(super.version(), "+interop");
return string.concat(super.version(), "+interop-beta.1");
}

/// @notice Converts `amount` of `from` token to `to` token.
Expand All @@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge {
function convert(address _from, address _to, uint256 _amount) external {
_validatePair(_from, _to);

MintableAndBurnable(_from).burn(msg.sender, _amount);
MintableAndBurnable(_to).mint(msg.sender, _amount);
IMintableAndBurnableERC20(_from).burn(msg.sender, _amount);
IMintableAndBurnableERC20(_to).mint(msg.sender, _amount);

emit Converted(_from, _to, msg.sender, _amount);
}
Expand Down
58 changes: 58 additions & 0 deletions packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";

// Interfaces
import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol";
import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";

/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000028
/// @title SuperchainERC20Bridge
/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the
/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain
/// binding.
contract SuperchainERC20Bridge is ISuperchainERC20Bridge {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy.
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
string public constant version = "1.0.0-beta.1";

/// @notice Sends tokens to some target address on another chain.
/// @param _token Token to send.
/// @param _to Address to send tokens to.
/// @param _amount Amount of tokens to send.
/// @param _chainId Chain ID of the destination chain.
function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external {
IMintableAndBurnableERC20(_token).burn(msg.sender, _amount);

bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount));
IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message);

emit SendERC20(_token, msg.sender, _to, _amount, _chainId);
}

/// @notice Relays tokens received from another chain.
/// @param _token Token to relay.
/// @param _from Address of the msg.sender of sendERC20 on the source chain.
/// @param _to Address to relay tokens to.
/// @param _amount Amount of tokens to relay.
function relayERC20(address _token, address _from, address _to, uint256 _amount) external {
if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger();

if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
revert InvalidCrossDomainSender();
}

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

IMintableAndBurnableERC20(_token).mint(_to, _amount);

emit RelayERC20(_token, _from, _to, _amount, source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title IMintableAndBurnableERC20
/// @notice Interface for mintable and burnable ERC20 tokens.
interface IMintableAndBurnableERC20 is IERC20 {
/// @notice Mints `_amount` of tokens to `_to`.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function mint(address _to, uint256 _amount) external;

/// @notice Burns `_amount` of tokens from `_from`.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function burn(address _from, uint256 _amount) external;
}
Loading

0 comments on commit ae024a5

Please sign in to comment.