Commit cb2066b0 authored by AgusDuha's avatar AgusDuha Committed by GitHub

feat: introduce SuperchainERC20 redesign + ICrosschainERC20 (#12321)

* feat: add superchain erc20 bridge (#61)

* feat: add superchain erc20 bridge

* fix: interfaces and versions

* refactor: optimism superchain erc20 redesign (#62)

* refactor: use oz upgradeable erc20 as dependency

* chore: update interfaces

* fix: tests based on changes

* refactor: remove op as dependency

* feat: add check for supererc20 bridge on modifier

* chore: update tests and interfaces

* chore: update stack vars name on test

* chore: remove empty gitmodules file

* chore: update superchain weth errors

* test: add superchain erc20 bridge tests (#65)

* test: add superchain erc20 bridge tests

* test: add optimism superchain erc20 beacon tests

* test: remove unnecessary test

* test: tests fixes

* test: tests fixes

* chore: update missing bridge on natspec (#69)

* chore: update missing bridge on natspec

* fix: natspecs

---------
Co-authored-by: default avataragusduha <agusnduha@gmail.com>

* fix: remove superchain erc20 base (#70)

* refactor: update isuperchainweth (#71)


---------
Co-authored-by: default avataragusduha <agusnduha@gmail.com>

* feat: rename mint/burn and add SuperchainERC20 (#74)

* refactor: rename mint and burn functions on superchain erc20

* chore: rename optimism superchain erc20 to superchain erc20

* feat: create optimism superchain erc20 contract

* chore: update natspec and errors

* fix: superchain erc20 tests

* refactor: make superchain erc20 abstract

* refactor: move storage and erc20 metadata functions to implementation

* chore: update interfaces

* chore: update superchain erc20 events

* fix: tests

* fix: natspecs

* fix: add semmver lock and snapshots

* fix: remove unused imports

* fix: natspecs

---------
Co-authored-by: default avatar0xDiscotech <131301107+0xDiscotech@users.noreply.github.com>

* fix: refactor zero check (#76)

* fix: pre pr

* fix: semver natspec check failure (#79)

* fix: semver natspec check failure

* fix: ignore mock contracts in semver natspec script

* fix: error message

* feat: add crosschain erc20 interface (#80)

* feat: add crosschain erc20 interface

* fix: refactor interfaces

* fix: superchain bridge natspec (#83)

* fix: superchain weth natspec (#84)
Co-authored-by: default avatar0xng <ng@defi.sucks>
Co-authored-by: default avatar0xParticle <particle@defi.sucks>
Co-authored-by: default avatargotzenx <78360669+gotzenx@users.noreply.github.com>

* fix: stop inheriting superchain interfaces (#85)

* fix: stop inheriting superchain interfaces

* fix: move events and erros into the implementation

* fix: make superchainERC20 inherits from crosschainERC20

* fix: superchain bridge rename (#86)

* fix: fee vault compiler error (#87)

* fix: remove unused imports

* fix: refactor common errors (#90)

* fix: refactor common errors

* fix: remove unused version

* fix: reuse unauthorized error (#92)

* fix: superchain erc20 factory conflicts

* fix: rename crosschain functions (#94)

---------
Co-authored-by: default avatarDisco <131301107+0xDiscotech@users.noreply.github.com>
Co-authored-by: default avatar0xng <ng@defi.sucks>
Co-authored-by: default avatar0xParticle <particle@defi.sucks>
Co-authored-by: default avatargotzenx <78360669+gotzenx@users.noreply.github.com>
parent c8edbe24
......@@ -4,13 +4,13 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm
GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099)
GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531)
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369245)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967385)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564368)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076583)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382)
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: 3512723)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72621)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921)
......
......@@ -157,6 +157,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("SuperchainTokenBridge"))) {
return payable(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
}
return payable(address(0));
}
......
......@@ -277,6 +277,7 @@ contract L2Genesis is Deployer {
setETHLiquidity(); // 25
setOptimismSuperchainERC20Factory(); // 26
setOptimismSuperchainERC20Beacon(); // 27
setSuperchainTokenBridge(); // 28
}
}
......@@ -601,6 +602,12 @@ contract L2Genesis is Deployer {
vm.resetNonce(address(beacon));
}
/// @notice This predeploy is following the safety invariant #1.
/// This contract has no initializer.
function setSuperchainTokenBridge() internal {
_setImplementationCode(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
}
/// @notice Sets all the preinstalls.
function setPreinstalls() public {
address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls"))));
......
......@@ -62,15 +62,14 @@ EXCLUDE_CONTRACTS=(
"ILegacyMintableERC20"
"IOptimismMintableERC20"
"IOptimismMintableERC721"
"IOptimismSuperchainERC20"
# Doesn't start with "I"
"MintableAndBurnable"
"KontrolCheatsBase"
# Currently inherit from interface, needs to be fixed.
"IWETH"
"IDelayedWETH"
"ISuperchainWETH"
"IL2ToL2CrossDomainMessenger"
"ICrossL2Inbox"
"ISystemConfigInterop"
......
......@@ -27,9 +27,8 @@ var excludeContracts = []string{
// TODO: Interfaces that need to be fixed
"IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20",
"IOptimismMintableERC721", "IOptimismSuperchainERC20", "MintableAndBurnable",
"KontrolCheatsBase", "IWETH", "IDelayedWETH", "IL2ToL2CrossDomainMessenger",
"ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy",
"IOptimismMintableERC721", "KontrolCheatsBase", "IWETH", "IDelayedWETH", "ISuperchainWETH",
"IL2ToL2CrossDomainMessenger", "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy",
}
type ContractDefinition struct {
......
......@@ -129,9 +129,14 @@ func run() error {
return
}
// Skip mock contracts
if strings.HasPrefix(contractName, "Mock") {
return
}
contractPath := contractFiles[contractName]
if contractPath == "" {
fail("%s: Source file not found", contractName)
fail("%s: Source file not found (For test mock contracts, prefix the name with 'Mock' to ignore this warning)", contractName)
return
}
......
......@@ -96,8 +96,8 @@
"sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84"
},
"src/L2/L2StandardBridgeInterop.sol": {
"initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef",
"sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994"
"initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6",
"sourceCodeHash": "0x36087332a4365ee172eed8fa35aa48e804b08279e97332058a588c2d4ae30c6b"
},
"src/L2/L2ToL1MessagePasser.sol": {
"initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d",
......@@ -108,24 +108,32 @@
"sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0x965af580568bad2b47d04c6ea536490aa263e9fcb5fb43e6c8bc00929fda3df5",
"sourceCodeHash": "0x9de349519900b1051f45d507b2fac1cf3f3ae8e2cfb1ceb56875a7ace1cb6ab8"
"initCodeHash": "0xd5c84e45746fd741d541a917ddc1cc0c7043c6b21d5c18040d4bc999d6a7b2db",
"sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7",
"sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e"
},
"src/L2/OptimismSuperchainERC20Factory.sol": {
"initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053",
"sourceCodeHash": "0x1e02d78a4e7ee93a07f7af7a78fe1773d0e87711f23a4ccd10a8692b47644a34"
"initCodeHash": "0x18a362c57f08b611db98dfde96121385e938f995c84e3547c1c03fd49f9db2fd",
"sourceCodeHash": "0x450cd89d0aae7bbc85ff57a14a6d3468c24c6743f25943f6d895d34b1456c456"
},
"src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0xcaadbf08057b5d47f7704257e9385a29e42a7a08c818646d109c5952d3d35218",
"sourceCodeHash": "0x05bbc6039e5a9ff38987e7b9b89c69e2ee8aa4b7ca20dd002ea1bbd3d70f27f3"
},
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0x75d061633a141af11a19b86e599a1725dfae8d245dcddfb6bb244a50d5e53f96"
},
"src/L2/SuperchainTokenBridge.sol": {
"initCodeHash": "0x07fc1d495928d9c13bd945a049d17e1d105d01c2082a7719e5d18cbc0e1c7d9e",
"sourceCodeHash": "0xaf2458e48dcadcafa8940cde7368549eae2280eef91247600d864ddac20f5d82"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x4ccd25f37a816205bc26f8532afa66e02f2b36ca7b7404d0fa48a4313ed16f0c",
"sourceCodeHash": "0xd186614f1515fa3ba2f43e401e639bfa3159603954e39a51769e9b57ad19a3fd"
"initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816",
"sourceCodeHash": "0x82d03262decf52d5954d40bca8703f96a0f3ba7accf6c1d75292856c2f34cf8f"
},
"src/L2/WETH.sol": {
"initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956",
......
......@@ -102,6 +102,42 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_from",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "crosschainBurn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "crosschainMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
......@@ -236,29 +272,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"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": [],
"name": "remoteToken",
......@@ -272,29 +285,6 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"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": [
{
......@@ -437,7 +427,7 @@
{
"indexed": true,
"internalType": "address",
"name": "account",
"name": "from",
"type": "address"
},
{
......@@ -450,26 +440,13 @@
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "account",
"name": "from",
"type": "address"
},
{
......@@ -479,18 +456,12 @@
"type": "uint256"
}
],
"name": "Mint",
"name": "CrosschainBurnt",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
......@@ -502,26 +473,27 @@
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
}
],
"name": "CrosschainMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "source",
"type": "uint256"
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "RelayERC20",
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
......@@ -533,15 +505,9 @@
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "destination",
"type": "uint256"
}
],
"name": "SendERC20",
"name": "Mint",
"type": "event"
},
{
......@@ -579,11 +545,6 @@
"name": "AllowanceUnderflow",
"type": "error"
},
{
"inputs": [],
"name": "CallerNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientAllowance",
......@@ -594,11 +555,6 @@
"name": "InsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInitialization",
......@@ -616,17 +572,17 @@
},
{
"inputs": [],
"name": "OnlyBridge",
"name": "PermitExpired",
"type": "error"
},
{
"inputs": [],
"name": "PermitExpired",
"name": "TotalSupplyOverflow",
"type": "error"
},
{
"inputs": [],
"name": "TotalSupplyOverflow",
"name": "Unauthorized",
"type": "error"
},
{
......
......@@ -37,7 +37,7 @@
"inputs": [
{
"internalType": "address",
"name": "_superchainToken",
"name": "_localToken",
"type": "address"
}
],
......
[
{
"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": [
{
"internalType": "bytes32",
"name": "msgHash_",
"type": "bytes32"
}
],
"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": "InvalidCrossDomainSender",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
}
]
\ No newline at end of file
......@@ -410,12 +410,17 @@
},
{
"inputs": [],
"name": "NotCustomGasToken",
"name": "CallerNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"name": "NotCustomGasToken",
"type": "error"
}
]
\ No newline at end of file
......@@ -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();
......@@ -18,26 +19,18 @@ error InvalidDecimals();
/// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory.
error InvalidLegacyERC20Address();
/// @notice Thrown when the SuperchainERC20 address is not found in the SuperchainERC20Factory.
/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory.
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
/// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for
/// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20)
/// and SuperchainERC20 tokens.
/// and OptimismSuperchainERC20 tokens.
contract L2StandardBridgeInterop is L2StandardBridge {
/// @notice Emitted when a conversion is made.
/// @param from The token being converted from.
......@@ -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.
......@@ -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);
}
......@@ -82,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge {
/// @notice Validates that the tokens are deployed by the correct factory.
/// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20).
/// @param _superAddr The SuperchainERC20 address.
/// @param _superAddr The OptimismSuperchainERC20 address.
function _validateFactories(address _legacyAddr, address _superAddr) internal view {
// 2. Valid legacy check
address _legacyRemoteToken =
IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr);
if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address();
// 3. Valid SuperchainERC20 check
// 3. Valid OptimismSuperchainERC20 check
address _superRemoteToken =
IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr);
if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address();
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol";
import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol";
/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge.
error OnlyBridge();
import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol";
/// @custom:proxied true
/// @title OptimismSuperchainERC20
/// @notice OptimismSuperchainERC20 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 L2StandardBridge to burn
/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a SuperchainERC20
/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse
/// conversion path.
/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a
/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it
/// also enables the inverse conversion path.
/// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding.
contract OptimismSuperchainERC20 is
IOptimismSuperchainERC20Extension,
SuperchainERC20,
ISemver,
Initializable,
ERC165
{
/// @notice Address of the StandardBridge Predeploy.
address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE;
contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 {
/// @notice Emitted whenever tokens are minted for an account.
/// @param to Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event Mint(address indexed to, uint256 amount);
/// @notice Emitted whenever tokens are burned from an account.
/// @param from Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event Burn(address indexed from, uint256 amount);
/// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at.
/// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff));
......@@ -55,15 +52,15 @@ contract OptimismSuperchainERC20 is
}
}
/// @notice A modifier that only allows the bridge to call
modifier onlyBridge() {
if (msg.sender != BRIDGE) revert OnlyBridge();
/// @notice A modifier that only allows the L2StandardBridge to call
modifier onlyL2StandardBridge() {
if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert Unauthorized();
_;
}
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.5
string public constant version = "1.0.0-beta.5";
/// @custom:semver 1.0.0-beta.6
string public constant override version = "1.0.0-beta.6";
/// @notice Constructs the OptimismSuperchainERC20 contract.
constructor() {
......@@ -94,7 +91,7 @@ contract OptimismSuperchainERC20 is
/// @notice Allows the L2StandardBridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function mint(address _to, uint256 _amount) external virtual onlyBridge {
function mint(address _to, uint256 _amount) external virtual onlyL2StandardBridge {
if (_to == address(0)) revert ZeroAddress();
_mint(_to, _amount);
......@@ -105,7 +102,7 @@ contract OptimismSuperchainERC20 is
/// @notice Allows the L2StandardBridge to burn tokens.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function burn(address _from, uint256 _amount) external virtual onlyBridge {
function burn(address _from, uint256 _amount) external virtual onlyL2StandardBridge {
if (_from == address(0)) revert ZeroAddress();
_burn(_from, _amount);
......@@ -114,7 +111,7 @@ contract OptimismSuperchainERC20 is
}
/// @notice Returns the address of the corresponding version of this token on the remote chain.
function remoteToken() public view override returns (address) {
function remoteToken() public view returns (address) {
return _getStorage().remoteToken;
}
......@@ -142,7 +139,6 @@ contract OptimismSuperchainERC20 is
/// @param _interfaceId Interface ID to check.
/// @return Whether or not the interface is supported by this contract.
function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
return
_interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId);
return _interfaceId == type(IOptimismSuperchainERC20).interfaceId || super.supportsInterface(_interfaceId);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
......@@ -13,13 +12,9 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol";
/// @title OptimismSuperchainERC20Factory
/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies
/// using CREATE3.
contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
/// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address.
/// This is used to keep track of the token deployments.
mapping(address _superchainToken => address remoteToken_) public deployments;
contract OptimismSuperchainERC20Factory is ISemver {
/// @notice Emitted when an OptimismSuperchainERC20 is deployed.
/// @param superchainToken Address of the SuperchainERC20 deployment.
/// @param superchainToken Address of the OptimismSuperchainERC20 deployment.
/// @param remoteToken Address of the corresponding token on the remote chain.
/// @param deployer Address of the account that deployed the token.
event OptimismSuperchainERC20Created(
......@@ -27,8 +22,12 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
);
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.3
string public constant version = "1.0.0-beta.3";
/// @custom:semver 1.0.0-beta.4
string public constant version = "1.0.0-beta.4";
/// @notice Mapping of the deployed OptimismSuperchainERC20 to the remote token address.
/// This is used to keep track of the token deployments.
mapping(address _localToken => address remoteToken_) public deployments;
/// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3.
/// @param _remoteToken Address of the remote token.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
import { Unauthorized } from "src/libraries/errors/CommonErrors.sol";
/// @title SuperchainERC20
/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token
/// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for
/// both replay protection and domain binding.
abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ISuperchainERC20Errors, ERC20 {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy.
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;
/// @notice Sends tokens to some target address on another chain.
/// @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 _to, uint256 _amount, uint256 _chainId) external virtual {
if (_to == address(0)) revert ZeroAddress();
_burn(msg.sender, _amount);
bytes memory _message = abi.encodeCall(this.relayERC20, (msg.sender, _to, _amount));
IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message);
emit SendERC20(msg.sender, _to, _amount, _chainId);
/// bridging to make it fungible across the Superchain. This construction allows the SuperchainTokenBridge to
/// burn and mint tokens.
abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver {
/// @notice A modifier that only allows the SuperchainTokenBridge to call
modifier onlySuperchainTokenBridge() {
if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized();
_;
}
/// @notice Relays tokens received from another chain.
/// @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 _from, address _to, uint256 _amount) external virtual {
if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger();
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
function version() external view virtual returns (string memory) {
return "1.0.0-beta.1";
}
if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
revert InvalidCrossDomainSender();
}
/// @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 onlySuperchainTokenBridge {
_mint(_to, _amount);
uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();
emit CrosschainMinted(_to, _amount);
}
_mint(_to, _amount);
/// @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 onlySuperchainTokenBridge {
_burn(_from, _amount);
emit RelayERC20(_from, _to, _amount, source);
emit CrosschainBurnt(_from, _amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol";
// Interfaces
import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000028
/// @title SuperchainTokenBridge
/// @notice The SuperchainTokenBridge 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 SuperchainTokenBridge {
/// @notice Thrown when attempting to relay a message and the cross domain message sender is not the
/// SuperchainTokenBridge.
error InvalidCrossDomainSender();
/// @notice Emitted when tokens are sent from one chain to another.
/// @param token Address of the token sent.
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Number of tokens sent.
/// @param destination Chain ID of the destination chain.
event SendERC20(
address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination
);
/// @notice Emitted whenever tokens are successfully relayed on this chain.
/// @param token Address of the token relayed.
/// @param from Address of the msg.sender of sendERC20 on the source chain.
/// @param to Address of the recipient.
/// @param amount Amount of tokens relayed.
/// @param source Chain ID of the source chain.
event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source);
/// @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 a target address on another chain.
/// @dev Tokens are burned on the source 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.
/// @return msgHash_ Hash of the message sent.
function sendERC20(
address _token,
address _to,
uint256 _amount,
uint256 _chainId
)
external
returns (bytes32 msgHash_)
{
if (_to == address(0)) revert ZeroAddress();
ISuperchainERC20(_token).crosschainBurn(msg.sender, _amount);
bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount));
msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message);
emit SendERC20(_token, msg.sender, _to, _amount, _chainId);
}
/// @notice Relays tokens received from another chain.
/// @dev Tokens are minted on the destination 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 Unauthorized();
if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
revert InvalidCrossDomainSender();
}
uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();
ISuperchainERC20(_token).crosschainMint(_to, _amount);
emit RelayERC20(_token, _from, _to, _amount, source);
}
}
......@@ -5,24 +5,25 @@ pragma solidity 0.8.15;
import { WETH98 } from "src/universal/WETH98.sol";
// Libraries
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
// Interfaces
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol";
import { IL1Block } from "src/L2/interfaces/IL1Block.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000024
/// @title SuperchainWETH
/// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains
/// within the superchain. SuperchainWETH can be converted into native ETH on chains that
/// do not use a custom gas token.
contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
contract SuperchainWETH is WETH98, ISuperchainWETH, ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.5
string public constant version = "1.0.0-beta.5";
/// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.6";
/// @inheritdoc WETH98
function deposit() public payable override {
......@@ -36,7 +37,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
super.withdraw(wad);
}
/// @inheritdoc ISuperchainERC20Extensions
/// @inheritdoc ISuperchainWETH
function sendERC20(address dst, uint256 wad, uint256 chainId) public {
// Burn from user's balance.
_burn(msg.sender, wad);
......@@ -57,12 +58,12 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
emit SendERC20(msg.sender, dst, wad, chainId);
}
/// @inheritdoc ISuperchainERC20Extensions
/// @inheritdoc ISuperchainWETH
function relayERC20(address from, address dst, uint256 wad) external {
// Receive message from other chain.
IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
if (msg.sender != address(messenger)) revert Unauthorized();
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();
if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger();
if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
// Mint from ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title ICrosschainERC20
/// @notice Defines the interface for crosschain ERC20 transfers.
interface ICrosschainERC20 {
/// @notice Emitted when a crosschain transfer mints tokens.
/// @param to Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event CrosschainMinted(address indexed to, uint256 amount);
/// @notice Emitted when a crosschain transfer burns tokens.
/// @param from Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event CrosschainBurnt(address indexed from, uint256 amount);
/// @notice 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 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;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol";
import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol";
interface IMintableAndBurnable is IERC20 {
function mint(address, uint256) external;
function burn(address, uint256) external;
}
interface IL2StandardBridgeInterop is IStandardBridge {
error InvalidDecimals();
error InvalidLegacyERC20Address();
......
// 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;
}
......@@ -2,38 +2,30 @@
pragma solidity ^0.8.0;
// Interfaces
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";
import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
/// @title IOptimismSuperchainERC20Extension
/// @title IOptimismSuperchainERC20
/// @notice This interface is available on the OptimismSuperchainERC20 contract.
/// We declare it as a separate interface so that it can be used in
/// custom implementations of SuperchainERC20.
interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors {
/// @notice Emitted whenever tokens are minted for an account.
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event Mint(address indexed account, uint256 amount);
/// @notice Emitted whenever tokens are burned from an account.
/// @param account Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event Burn(address indexed account, uint256 amount);
/// @notice Allows the L2StandardBridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
interface IOptimismSuperchainERC20 is ISuperchainERC20 {
error ZeroAddress();
error InvalidInitialization();
error NotInitializing();
event Initialized(uint64 version);
event Mint(address indexed to, uint256 amount);
event Burn(address indexed from, uint256 amount);
function initialize(address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals) external;
function mint(address _to, uint256 _amount) external;
/// @notice Allows the L2StandardBridge to burn tokens.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function burn(address _from, uint256 _amount) external;
/// @notice Returns the address of the corresponding version of this token on the remote chain.
function remoteToken() external view returns (address);
}
/// @title IOptimismSuperchainERC20
/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface.
interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { }
function supportsInterface(bytes4 _interfaceId) external view returns (bool);
function __constructor__() external;
}
......@@ -11,8 +11,6 @@ interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
address indexed superchainToken, address indexed remoteToken, address deployer
);
function deployments(address _superchainToken) external view override returns (address remoteToken_);
function version() external view override returns (string memory);
function deploy(
address _remoteToken,
string memory _name,
......
......@@ -2,57 +2,15 @@
pragma solidity ^0.8.0;
// Interfaces
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";
/// @title ISuperchainERC20Extensions
/// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20.
/// Exists in case developers are already importing the ERC20 interface separately and
/// importing the full SuperchainERC20 interface would cause conflicting imports.
interface ISuperchainERC20Extensions {
/// @notice Emitted when tokens are sent from one chain to another.
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Number of tokens sent.
/// @param destination Chain ID of the destination chain.
event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination);
/// @notice Emitted whenever tokens are successfully relayed on this chain.
/// @param from Address of the msg.sender of sendERC20 on the source chain.
/// @param to Address of the recipient.
/// @param amount Amount of tokens relayed.
/// @param source Chain ID of the source chain.
event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source);
/// @notice Sends tokens to some target address on another chain.
/// @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 _to, uint256 _amount, uint256 _chainId) external;
/// @notice Relays tokens received from another chain.
/// @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 _from, address _to, uint256 _amount) external;
}
/// @title ISuperchainERC20Errors
/// @notice Interface containing the errors added in the SuperchainERC20 implementation.
interface ISuperchainERC20Errors {
/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
/// L2ToL2CrossDomainMessenger.
error CallerNotL2ToL2CrossDomainMessenger();
/// @notice Thrown when attempting to relay a message and the cross domain message sender is not this
/// SuperchainERC20.
error InvalidCrossDomainSender();
/// @notice Thrown when attempting to perform an operation and the account is the zero address.
error ZeroAddress();
}
import { ISemver } from "src/universal/interfaces/ISemver.sol";
/// @title ISuperchainERC20
/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface.
interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors {
/// @notice This interface is available on the SuperchainERC20 contract.
/// @dev This interface is needed for the abstract SuperchainERC20 implementation but is not part of the standard
interface ISuperchainERC20 is ICrosschainERC20, IERC20Solady, ISemver {
error Unauthorized();
function __constructor__() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { ISemver } from "src/universal/interfaces/ISemver.sol";
/// @title ISuperchainTokenBridge
/// @notice Interface for the SuperchainTokenBridge contract.
interface ISuperchainTokenBridge is ISemver {
error ZeroAddress();
error Unauthorized();
error InvalidCrossDomainSender();
event SendERC20(
address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination
);
event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source);
function sendERC20(
address _token,
address _to,
uint256 _amount,
uint256 _chainId
)
external
returns (bytes32 msgHash_);
function relayERC20(address _token, address _from, address _to, uint256 _amount) external;
function __constructor__() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { IWETH } from "src/universal/interfaces/IWETH.sol";
interface ISuperchainWETH {
/// @notice Thrown when attempting a deposit or withdrawal and the chain uses a custom gas token.
error NotCustomGasToken();
error Unauthorized();
event Approval(address indexed src, address indexed guy, uint256 wad);
event Deposit(address indexed dst, uint256 wad);
/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
/// L2ToL2CrossDomainMessenger.
error CallerNotL2ToL2CrossDomainMessenger();
/// @notice Thrown when attempting to relay a message and the cross domain message sender is not `address(this)`
error InvalidCrossDomainSender();
/// @notice Emitted whenever tokens are successfully relayed on this chain.
/// @param from Address of the msg.sender of sendERC20 on the source chain.
/// @param to Address of the recipient.
/// @param amount Amount of tokens relayed.
/// @param source Chain ID of the source chain.
event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source);
/// @notice Emitted when tokens are sent from one chain to another.
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Number of tokens sent.
/// @param destination Chain ID of the destination chain.
event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination);
event Transfer(address indexed src, address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
fallback() external payable;
receive() external payable;
function allowance(address, address) external view returns (uint256);
function approve(address guy, uint256 wad) external returns (bool);
function balanceOf(address) external view returns (uint256);
function decimals() external view returns (uint8);
function deposit() external payable;
function name() external view returns (string memory);
function relayERC20(address from, address dst, uint256 wad) external;
function sendERC20(address dst, uint256 wad, uint256 chainId) external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address dst, uint256 wad) external returns (bool);
function transferFrom(address src, address dst, uint256 wad) external returns (bool);
function version() external view returns (string memory);
function withdraw(uint256 wad) external;
function __constructor__() external;
/// @notice Sends tokens to some target address on another chain.
/// @param _dst Address to send tokens to.
/// @param _wad Amount of tokens to send.
/// @param _chainId Chain ID of the destination chain.
function sendERC20(address _dst, uint256 _wad, uint256 _chainId) external;
/// @notice Relays tokens received from another chain.
/// @param _from Address of the msg.sender of sendERC20 on the source chain.
/// @param _dst Address to relay tokens to.
/// @param _wad Amount of tokens to relay.
function relayERC20(address _from, address _dst, uint256 _wad) external;
}
interface ISuperchainWETHERC20 is IWETH, ISuperchainWETH { }
......@@ -105,6 +105,9 @@ library Predeploys {
/// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract.
address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28;
/// @notice Address of the SuperchainTokenBridge predeploy.
address internal constant SUPERCHAIN_TOKEN_BRIDGE = 0x4200000000000000000000000000000000000028;
/// @notice Returns the name of the predeploy at the given address.
function getName(address _addr) internal pure returns (string memory out_) {
require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy");
......@@ -135,6 +138,7 @@ library Predeploys {
if (_addr == ETH_LIQUIDITY) return "ETHLiquidity";
if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory";
if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon";
if (_addr == SUPERCHAIN_TOKEN_BRIDGE) return "SuperchainTokenBridge";
revert("Predeploys: unnamed predeploy");
}
......@@ -154,7 +158,8 @@ library Predeploys {
|| (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER)
|| (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY)
|| (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY)
|| (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON);
|| (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON)
|| (_useInterop && _addr == SUPERCHAIN_TOKEN_BRIDGE);
}
function isPredeployNamespace(address _addr) internal pure returns (bool) {
......
......@@ -12,3 +12,6 @@ error NotCustomGasToken();
/// @notice Error for when a transfer via call fails.
error TransferFailed();
/// @notice Thrown when attempting to perform an operation and the account is the zero address.
error ZeroAddress();
......@@ -150,8 +150,8 @@ contract L2GenesisTest is Test {
// 2 predeploys do not have proxies
assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2);
// 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false
assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17);
// 24 proxies have the implementation set if useInterop is true and 17 if useInterop is false
assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 24 : 17);
// All proxies except 2 have the proxy 1967 admin slot set to the proxy admin
assertEq(
......
......@@ -5,7 +5,8 @@ pragma solidity 0.8.15;
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
// Interfaces
import { IL2StandardBridgeInterop, IMintableAndBurnable } from "src/L2/interfaces/IL2StandardBridgeInterop.sol";
import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol";
import { IL2StandardBridgeInterop } from "src/L2/interfaces/IL2StandardBridgeInterop.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol";
......@@ -53,9 +54,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer {
}
}
/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token
/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token
contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test {
/// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token
/// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token
function _setUpLegacyToSuper(address _from, address _to) internal {
// Assume
_assumeAddress(_from);
......@@ -198,9 +199,11 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T
// Mock and expect the `burn` and `mint` functions
_mockAndExpect(
_from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode()
_from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode()
);
_mockAndExpect(
_to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode()
);
_mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode());
// Act
vm.prank(_caller);
......@@ -208,9 +211,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T
}
}
/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token
/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token
contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test {
/// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token
/// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token
function _setUpSuperToLegacy(address _from, address _to) internal {
// Assume
_assumeAddress(_from);
......@@ -354,9 +357,11 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T
// Mock and expect the `burn` and `mint` functions
_mockAndExpect(
_from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode()
_from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode()
);
_mockAndExpect(
_to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode()
);
_mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode());
// Act
vm.prank(_caller);
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
pragma solidity 0.8.15;
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol";
import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";
// Target contract
import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol";
import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
/// @title OptimismSuperchainERC20FactoryTest
/// @notice Contract for testing the OptimismSuperchainERC20Factory contract.
contract OptimismSuperchainERC20FactoryTest is Test {
contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer {
using Bytes32AddressLib for bytes32;
OptimismSuperchainERC20 public superchainERC20Impl;
OptimismSuperchainERC20Factory public superchainERC20Factory;
event OptimismSuperchainERC20Created(
address indexed superchainToken, address indexed remoteToken, address deployer
);
/// @notice Sets up the test suite.
function setUp() public {
superchainERC20Impl = new OptimismSuperchainERC20();
// Deploy the OptimismSuperchainERC20Beacon contract
_deployBeacon();
superchainERC20Factory = new OptimismSuperchainERC20Factory();
}
/// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract
function _deployBeacon() internal {
// Deploy the OptimismSuperchainERC20Beacon implementation
address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON;
address _impl = Predeploys.predeployToCodeNamespace(_addr);
vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon"));
// Deploy the ERC1967Proxy contract at the Predeploy
bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy");
vm.etch(_addr, code);
EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN);
EIP1967Helper.setImplementation(_addr, _impl);
// Mock implementation address
vm.mockCall(
_impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl))
);
function setUp() public override {
super.enableInterop();
super.setUp();
}
/// @notice Test that calling `deploy` with valid parameters succeeds.
......@@ -62,22 +38,22 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{
// Arrange
bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals));
address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory));
address deployment = _calculateTokenAddress(salt, address(l2OptimismSuperchainERC20Factory));
vm.expectEmit(address(superchainERC20Factory));
emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller);
vm.expectEmit(address(l2OptimismSuperchainERC20Factory));
emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller);
// Act
vm.prank(_caller);
address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
// Assert
assertTrue(addr == deployment);
assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals);
assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken);
assertEq(OptimismSuperchainERC20(deployment).name(), _name);
assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol);
assertEq(superchainERC20Factory.deployments(deployment), _remoteToken);
assertTrue(IERC20Metadata(deployment).decimals() == _decimals);
assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken);
assertEq(IERC20Metadata(deployment).name(), _name);
assertEq(IERC20Metadata(deployment).symbol(), _symbol);
assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken);
}
/// @notice Test that calling `deploy` with the same parameters twice reverts.
......@@ -92,13 +68,13 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{
// Arrange
vm.prank(_caller);
superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
vm.expectRevert(bytes("DEPLOYMENT_FAILED"));
// Act
vm.prank(_caller);
superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
}
/// @notice Precalculates the address of the token contract using CREATE3.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing utilities
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
// Target contract
import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge.sol";
import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
/// @title SuperchainTokenBridgeTest
/// @notice Contract for testing the SuperchainTokenBridge contract.
contract SuperchainTokenBridgeTest is Bridge_Initializer {
address internal constant ZERO_ADDRESS = address(0);
string internal constant NAME = "SuperchainERC20";
string internal constant SYMBOL = "OSE";
address internal constant REMOTE_TOKEN = address(0x123);
event Transfer(address indexed from, address indexed to, uint256 value);
event SendERC20(
address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination
);
event RelayERC20(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 source);
ISuperchainERC20 public superchainERC20;
/// @notice Sets up the test suite.
function setUp() public override {
super.enableInterop();
super.setUp();
superchainERC20 = ISuperchainERC20(
IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deploy(
REMOTE_TOKEN, NAME, SYMBOL, 18
)
);
}
/// @notice Helper function to setup a mock and expect a call to it.
function _mockAndExpect(address _receiver, bytes memory _calldata, bytes memory _returned) internal {
vm.mockCall(_receiver, _calldata, _returned);
vm.expectCall(_receiver, _calldata);
}
/// @notice Tests the `sendERC20` function reverts when the address `_to` is zero.
function testFuzz_sendERC20_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public {
// Expect the revert with `ZeroAddress` selector
vm.expectRevert(ISuperchainTokenBridge.ZeroAddress.selector);
// Call the `sendERC20` function with the zero address as `_to`
vm.prank(_sender);
superchainTokenBridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId);
}
/// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20`
/// event.
function testFuzz_sendERC20_succeeds(
address _sender,
address _to,
uint256 _amount,
uint256 _chainId,
bytes32 _msgHash
)
external
{
// Ensure `_sender` and `_to` is not the zero address
vm.assume(_sender != ZERO_ADDRESS);
vm.assume(_to != ZERO_ADDRESS);
// Mint some tokens to the sender so then they can be sent
vm.prank(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
superchainERC20.crosschainMint(_sender, _amount);
// Get the total supply and balance of `_sender` before the send to compare later on the assertions
uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply();
uint256 _senderBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_sender);
// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit Transfer(_sender, ZERO_ADDRESS, _amount);
// Look for the emit of the `SendERC20` event
vm.expectEmit(address(superchainTokenBridge));
emit SendERC20(address(superchainERC20), _sender, _to, _amount, _chainId);
// Mock the call over the `sendMessage` function and expect it to be called properly
bytes memory _message =
abi.encodeCall(superchainTokenBridge.relayERC20, (address(superchainERC20), _sender, _to, _amount));
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(
IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainTokenBridge), _message
),
abi.encode(_msgHash)
);
// Call the `sendERC20` function
vm.prank(_sender);
bytes32 _returnedMsgHash = superchainTokenBridge.sendERC20(address(superchainERC20), _to, _amount, _chainId);
// Check the message hash was generated correctly
assertEq(_msgHash, _returnedMsgHash);
// Check the total supply and balance of `_sender` after the send were updated correctly
assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore - _amount);
assertEq(IERC20(address(superchainERC20)).balanceOf(_sender), _senderBalanceBefore - _amount);
}
/// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger.
function testFuzz_relayERC20_notMessenger_reverts(
address _token,
address _caller,
address _to,
uint256 _amount
)
public
{
// Ensure the caller is not the messenger
vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
// Expect the revert with `Unauthorized` selector
vm.expectRevert(ISuperchainTokenBridge.Unauthorized.selector);
// Call the `relayERC20` function with the non-messenger caller
vm.prank(_caller);
superchainTokenBridge.relayERC20(_token, _caller, _to, _amount);
}
/// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not
/// the same SuperchainTokenBridge.
function testFuzz_relayERC20_notCrossDomainSender_reverts(
address _token,
address _crossDomainMessageSender,
address _to,
uint256 _amount
)
public
{
vm.assume(_crossDomainMessageSender != address(superchainTokenBridge));
// Mock the call over the `crossDomainMessageSender` function setting a wrong sender
vm.mockCall(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(_crossDomainMessageSender)
);
// Expect the revert with `InvalidCrossDomainSender` selector
vm.expectRevert(ISuperchainTokenBridge.InvalidCrossDomainSender.selector);
// Call the `relayERC20` function with the sender caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainTokenBridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount);
}
/// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event.
function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public {
vm.assume(_to != ZERO_ADDRESS);
// Mock the call over the `crossDomainMessageSender` function setting the same address as value
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector),
abi.encode(address(superchainTokenBridge))
);
// Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector),
abi.encode(_source)
);
// Get the total supply and balance of `_to` before the relay to compare later on the assertions
uint256 _totalSupplyBefore = IERC20(address(superchainERC20)).totalSupply();
uint256 _toBalanceBefore = IERC20(address(superchainERC20)).balanceOf(_to);
// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit Transfer(ZERO_ADDRESS, _to, _amount);
// Look for the emit of the `RelayERC20` event
vm.expectEmit(address(superchainTokenBridge));
emit RelayERC20(address(superchainERC20), _from, _to, _amount, _source);
// Call the `relayERC20` function with the messenger caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainTokenBridge.relayERC20(address(superchainERC20), _from, _to, _amount);
// Check the total supply and balance of `_to` after the relay were updated correctly
assertEq(IERC20(address(superchainERC20)).totalSupply(), _totalSupplyBefore + _amount);
assertEq(IERC20(address(superchainERC20)).balanceOf(_to), _toBalanceBefore + _amount);
}
}
......@@ -6,11 +6,12 @@ import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
// Interfaces
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
/// @title SuperchainWETH_Test
/// @notice Contract for testing the SuperchainWETH contract.
......@@ -320,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest {
// Nothing to arrange.
// Act
vm.expectRevert(Unauthorized.selector);
vm.expectRevert(ISuperchainWETH.CallerNotL2ToL2CrossDomainMessenger.selector);
vm.prank(alice);
superchainWeth.relayERC20(_sender, bob, _amount);
......@@ -345,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest {
);
// Act
vm.expectRevert(Unauthorized.selector);
vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector);
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainWeth.relayERC20(_sender, bob, _amount);
......
......@@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol";
import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol";
import { HandlerGetters } from "./helpers/HandlerGetters.t.sol";
......@@ -38,7 +38,7 @@ contract OptimismSuperchainERC20Properties is Test {
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply();
totalSupply += SuperchainERC20(supertoken).totalSupply();
}
}
assertEq(trackedSupply, totalSupply + fundsInTransit);
......@@ -61,7 +61,7 @@ contract OptimismSuperchainERC20Properties is Test {
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply();
totalSupply += SuperchainERC20(supertoken).totalSupply();
}
}
assertEq(trackedSupply, totalSupply);
......@@ -69,7 +69,7 @@ contract OptimismSuperchainERC20Properties is Test {
}
/// @custom:invariant many other assertion mode invariants are also defined under
/// `test/invariants/OptimismSuperchainERC20/fuzz/` .
/// `test/invariants/SuperchainERC20/fuzz/` .
///
/// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the
/// handler for assertion test failures
......
# Supertoken advanced testing
## Note
This campaign will need to be updated the redesign `OptimismSuperchainERC20` redesign. Please delete this comment once the update is done.
## Milestones
The supertoken ecosystem consists of not just the supertoken contract, but the required changes to other contracts for liquidity to reach the former.
......@@ -12,8 +16,8 @@ Considering only the supertoken contract is merged into the `develop` branch, an
## Definitions
- *legacy token:* an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping.
- *supertoken:* a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory`
- _legacy token:_ an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping.
- _supertoken:_ a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory`
# Ecosystem properties
......@@ -28,7 +32,7 @@ legend:
## Unit test
| id | milestone | description | tested |
| --- | --- | --- | --- |
| --- | ------------------- | ------------------------------------------------------------------------------------------ | ------ |
| 0 | Factories | supertoken token address does not depend on the executing chain’s chainID | [ ] |
| 1 | Factories | supertoken token address depends on remote token, name, symbol and decimals | [ ] |
| 2 | Liquidity Migration | convert() should only allow converting legacy tokens to supertoken and viceversa | [ ] |
......@@ -40,18 +44,18 @@ legend:
## Valid state
| id | milestone | description | tested |
| --- | --- | --- | --- |
| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [x] |
| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [~] |
| --- | --------- | ------------------------------------------------------------------------------ | ------ |
| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [] |
| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [] |
## Variable transition
| id | milestone | description | tested |
| --- | --- | --- | --- |
| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [x] |
| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [x] |
| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [x] |
| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [x] |
| --- | ------------------- | ------------------------------------------------------------------------------------------------- | ------ |
| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [] |
| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [] |
| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [] |
| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [] |
| 27 | SupERC20 | relayERC20 increases sender's balance in the destination chain exactly by the input amount | [x] |
| 11 | SupERC20 | relayERC20 increases the token's totalSupply in the destination chain exactly by the input amount | [ ] |
| 12 | Liquidity Migration | supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge | [~] |
......@@ -63,9 +67,9 @@ legend:
## High level
| id | milestone | description | tested |
| --- | --- | --- | --- |
| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] |
| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] |
| --- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] |
| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] |
| 19 | Liquidity Migration | sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy) | [~] |
| 20 | SupERC20 | tokens sendERC20-ed on a source chain to a destination chain can be relayERC20-ed on it as long as the source chain is in the dependency set of the destination chain | [ ] |
| 21 | Liquidity Migration | sum of supertoken total supply across all chains is = to convert(legacy, super)- convert(super, legacy) when all cross-chain messages are processed | [~] |
......@@ -76,7 +80,7 @@ As another layer of defense, the following properties are defined which assume b
It’s worth noting that these properties will not hold for a live system
| id | milestone | description | tested |
| --- | --- | --- | --- |
| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [x] |
| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [x] |
| --- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------ |
| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [] |
| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [] |
| 24 | Liquidity Migration | sum of supertoken total supply across all chains is always equal to convert(legacy, super)- convert(super, legacy) | [~] |
......@@ -2,7 +2,7 @@
pragma solidity ^0.8.25;
import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { ProtocolHandler } from "../handlers/Protocol.t.sol";
import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol";
......@@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
validateTokenDeployParams(params)
{
chainId = bound(chainId, 0, MAX_CHAINS - 1);
OptimismSuperchainERC20 supertoken = _deploySupertoken(
SuperchainERC20 supertoken = _deploySupertoken(
remoteTokens[params.remoteTokenIndex],
WORDS[params.nameIndex],
WORDS[params.symbolIndex],
......@@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
compatibleAssert(supertoken.totalSupply() == 0);
}
/// @custom:property-id 6
/// @custom:property calls to sendERC20 succeed as long as caller has enough balance
/// @custom:property-id 22
/// @custom:property sendERC20 decreases sender balance in source chain and increases receiver balance in
/// destination chain exactly by the input amount
/// @custom:property-id 23
/// @custom:property sendERC20 decreases total supply in source chain and increases it in destination chain exactly
/// by the input amount
function fuzz_bridgeSupertokenAtomic(
uint256 fromIndex,
uint256 recipientIndex,
uint256 destinationChainId,
uint256 amount
)
public
withActor(msg.sender)
{
destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1);
fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1);
address recipient = getActorByRawIndex(recipientIndex);
OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]);
OptimismSuperchainERC20 destinationToken =
MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId);
uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor());
uint256 sourceSupplyBefore = sourceToken.totalSupply();
uint256 destinationBalanceBefore = destinationToken.balanceOf(recipient);
uint256 destinationSupplyBefore = destinationToken.totalSupply();
MESSENGER.setAtomic(true);
vm.prank(currentActor());
try sourceToken.sendERC20(recipient, amount, destinationChainId) {
MESSENGER.setAtomic(false);
uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor());
uint256 destinationBalanceAfter = destinationToken.balanceOf(recipient);
// no free mint
compatibleAssert(
sourceBalanceBefore + destinationBalanceBefore == sourceBalanceAfter + destinationBalanceAfter
);
// 22
compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter);
compatibleAssert(destinationBalanceBefore + amount == destinationBalanceAfter);
uint256 sourceSupplyAfter = sourceToken.totalSupply();
uint256 destinationSupplyAfter = destinationToken.totalSupply();
// 23
compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter);
compatibleAssert(destinationSupplyBefore + amount == destinationSupplyAfter);
} catch {
MESSENGER.setAtomic(false);
// 6
compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount);
}
}
/// @custom:property-id 6
/// @custom:property calls to sendERC20 succeed as long as caller has enough balance
/// @custom:property-id 26
/// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount
/// @custom:property-id 10
/// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount
function fuzz_sendERC20(
uint256 fromIndex,
uint256 recipientIndex,
uint256 destinationChainId,
uint256 amount
)
public
withActor(msg.sender)
{
destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1);
fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1);
address recipient = getActorByRawIndex(recipientIndex);
OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]);
OptimismSuperchainERC20 destinationToken =
MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId);
bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken));
uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor());
uint256 sourceSupplyBefore = sourceToken.totalSupply();
vm.prank(currentActor());
try sourceToken.sendERC20(recipient, amount, destinationChainId) {
(, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt);
ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount);
// 26
uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor());
compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter);
// 10
uint256 sourceSupplyAfter = sourceToken.totalSupply();
compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter);
} catch {
// 6
compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount);
}
}
/// @custom:property-id 11
/// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input
/// amount
......@@ -135,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
/// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid
function fuzz_relayERC20(uint256 messageIndex) external {
MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex);
OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender);
SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender);
uint256 destinationSupplyBefore = destinationToken.totalSupply();
uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient);
......@@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
compatibleAssert(false);
}
}
/// @custom:property-id 8
/// @custom:property calls to sendERC20 with a value of zero dont modify accounting
// @notice is a subset of fuzz_sendERC20, so we'll just call it
// instead of re-implementing it. Keeping the function for visibility of the property.
function fuzz_sendZeroDoesNotModifyAccounting(
uint256 fromIndex,
uint256 recipientIndex,
uint256 destinationChainId
)
external
{
fuzz_sendERC20(fromIndex, recipientIndex, destinationChainId, 0);
}
/// @custom:property-id 9
/// @custom:property calls to relayERC20 with a value of zero dont modify accounting
/// @custom:property-id 7
/// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid
/// @notice cant call fuzz_RelayERC20 internally since that pops a
/// random message, which we cannot guarantee has a value of zero
function fuzz_relayZeroDoesNotModifyAccounting(
uint256 fromIndex,
uint256 recipientIndex
)
external
withActor(msg.sender)
{
fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1);
address recipient = getActorByRawIndex(recipientIndex);
OptimismSuperchainERC20 token = OptimismSuperchainERC20(allSuperTokens[fromIndex]);
uint256 balanceSenderBefore = token.balanceOf(currentActor());
uint256 balanceRecipientBefore = token.balanceOf(recipient);
uint256 supplyBefore = token.totalSupply();
MESSENGER.setCrossDomainMessageSender(address(token));
vm.prank(address(MESSENGER));
try token.relayERC20(currentActor(), recipient, 0) {
MESSENGER.setCrossDomainMessageSender(address(0));
} catch {
// should not revert because of 7, and if it *does* revert, I want the test suite
// to discard the sequence instead of potentially getting another
// error due to the crossDomainMessageSender being manually set
compatibleAssert(false);
}
uint256 balanceSenderAfter = token.balanceOf(currentActor());
uint256 balanceRecipeintAfter = token.balanceOf(recipient);
uint256 supplyAfter = token.totalSupply();
compatibleAssert(balanceSenderBefore == balanceSenderAfter);
compatibleAssert(balanceRecipientBefore == balanceRecipeintAfter);
compatibleAssert(supplyBefore == supplyAfter);
}
}
......@@ -10,78 +10,6 @@ import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol";
contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
/// @custom:property-id 7
/// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid
/// @notice this ensures actors cant simply call relayERC20 and get tokens, no matter the system state
/// but there's still some possible work on how hard we can bork the system state with handlers calling
/// the L2ToL2CrossDomainMessenger or bridge directly (pending on non-atomic bridging)
function fuzz_relayERC20(
uint256 tokenIndex,
address sender,
address crossDomainMessageSender,
address recipient,
uint256 amount
)
external
{
MESSENGER.setCrossDomainMessageSender(crossDomainMessageSender);
address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)];
vm.prank(sender);
try OptimismSuperchainERC20(token).relayERC20(sender, recipient, amount) {
MESSENGER.setCrossDomainMessageSender(address(0));
compatibleAssert(sender == address(MESSENGER));
compatibleAssert(crossDomainMessageSender == token);
// this increases the supply across chains without a call to
// `mint` by the MESSENGER, so it kind of breaks an invariant, but
// let's walk around that:
bytes32 salt = MESSENGER.superTokenInitDeploySalts(token);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue + amount);
} catch {
compatibleAssert(sender != address(MESSENGER) || crossDomainMessageSender != token);
MESSENGER.setCrossDomainMessageSender(address(0));
}
}
/// @custom:property-id 6
/// @custom:property calls to sendERC20 succeed as long as caller has enough balance
/// @custom:property-id 26
/// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount
/// @custom:property-id 10
/// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount
function fuzz_sendERC20(
address sender,
address recipient,
uint256 fromIndex,
uint256 destinationChainId,
uint256 amount
)
public
{
destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1);
OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]);
OptimismSuperchainERC20 destinationToken =
MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId);
bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken));
uint256 sourceBalanceBefore = sourceToken.balanceOf(sender);
uint256 sourceSupplyBefore = sourceToken.totalSupply();
vm.prank(sender);
try sourceToken.sendERC20(recipient, amount, destinationChainId) {
(, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt);
ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount);
// 26
uint256 sourceBalanceAfter = sourceToken.balanceOf(sender);
compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter);
// 10
uint256 sourceSupplyAfter = sourceToken.totalSupply();
compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter);
} catch {
// 6
compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount);
}
}
/// @custom:property-id 12
/// @custom:property supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge
function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external {
......@@ -89,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).mint(to, amount) {
try OptimismSuperchainERC20(token).crosschainMint(to, amount) {
compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue + amount);
......@@ -105,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).burn(from, amount) {
try OptimismSuperchainERC20(token).crosschainBurn(from, amount) {
compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue - amount);
......
......@@ -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).mint(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);
......@@ -164,7 +164,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors {
bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId));
supertoken = OptimismSuperchainERC20(
address(
// TODO: Use the SuperchainERC20 Beacon Proxy
// TODO: Use the OptimismSuperchainERC20 Beacon Proxy
new ERC1967Proxy{ salt: hackySalt }(
address(superchainERC20Impl),
abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals))
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
contract MockL2ToL2CrossDomainMessenger {
......@@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger {
)
external
view
returns (OptimismSuperchainERC20)
returns (SuperchainERC20)
{
return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]);
return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]);
}
function setCrossDomainMessageSender(address sender) external {
......
......@@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
// Interfaces
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
/// @title SuperchainWETH_User
......@@ -29,7 +29,7 @@ contract SuperchainWETH_User is StdUtils {
Vm internal vm;
/// @notice The SuperchainWETH contract.
ISuperchainWETH internal weth;
ISuperchainWETHERC20 internal weth;
/// @notice Mapping of sent messages.
mapping(bytes32 => bool) internal sent;
......@@ -40,7 +40,7 @@ contract SuperchainWETH_User is StdUtils {
/// @param _vm The Vm contract.
/// @param _weth The SuperchainWETH contract.
/// @param _balance The initial balance of the contract.
constructor(Vm _vm, ISuperchainWETH _weth, uint256 _balance) {
constructor(Vm _vm, ISuperchainWETHERC20 _weth, uint256 _balance) {
vm = _vm;
weth = _weth;
vm.deal(address(this), _balance);
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
/// @title SuperchainERC20Implementation Mock contract
/// @notice Mock contract just to create tests over an implementation of the SuperchainERC20 abstract contract.
contract MockSuperchainERC20Implementation is SuperchainERC20 {
function name() public pure override returns (string memory) {
return "SuperchainERC20";
}
function symbol() public pure override returns (string memory) {
return "SCE";
}
}
......@@ -37,17 +37,18 @@ import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol
import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol";
import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol";
import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol";
import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol";
import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol";
import { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol";
import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol";
import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol";
import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol";
import { IL1Block } from "src/L2/interfaces/IL1Block.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { IWETH } from "src/universal/interfaces/IWETH.sol";
import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol";
import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol";
import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge.sol";
/// @title Setup
/// @dev This contact is responsible for setting up the contracts in state. It currently
......@@ -105,12 +106,11 @@ contract Setup {
IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN);
ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER);
IWETH weth = IWETH(payable(Predeploys.WETH));
ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH));
ISuperchainWETHERC20 superchainWeth = ISuperchainWETHERC20(payable(Predeploys.SUPERCHAIN_WETH));
IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY);
// TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas
IOptimismERC20Factory l2OptimismSuperchainERC20Factory =
IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
ISuperchainTokenBridge superchainTokenBridge = ISuperchainTokenBridge(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory =
IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
/// @dev Deploys the Deploy contract without including its bytecode in the bytecode
/// of this contract by fetching the bytecode dynamically using `vm.getCode()`.
......@@ -236,6 +236,7 @@ contract Setup {
labelPredeploy(Predeploys.ETH_LIQUIDITY);
labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON);
labelPredeploy(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
// L2 Preinstalls
labelPreinstall(Preinstalls.MultiCall3);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment