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 ...@@ -4,13 +4,13 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm
GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099)
GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531)
GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369245) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967385) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564368) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076583) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) 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_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921)
......
...@@ -157,6 +157,8 @@ abstract contract Artifacts { ...@@ -157,6 +157,8 @@ abstract contract Artifacts {
return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
} else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) {
return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON);
} else if (digest == keccak256(bytes("SuperchainTokenBridge"))) {
return payable(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
} }
return payable(address(0)); return payable(address(0));
} }
......
...@@ -277,6 +277,7 @@ contract L2Genesis is Deployer { ...@@ -277,6 +277,7 @@ contract L2Genesis is Deployer {
setETHLiquidity(); // 25 setETHLiquidity(); // 25
setOptimismSuperchainERC20Factory(); // 26 setOptimismSuperchainERC20Factory(); // 26
setOptimismSuperchainERC20Beacon(); // 27 setOptimismSuperchainERC20Beacon(); // 27
setSuperchainTokenBridge(); // 28
} }
} }
...@@ -601,6 +602,12 @@ contract L2Genesis is Deployer { ...@@ -601,6 +602,12 @@ contract L2Genesis is Deployer {
vm.resetNonce(address(beacon)); 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. /// @notice Sets all the preinstalls.
function setPreinstalls() public { function setPreinstalls() public {
address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls")))); address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls"))));
......
...@@ -62,15 +62,14 @@ EXCLUDE_CONTRACTS=( ...@@ -62,15 +62,14 @@ EXCLUDE_CONTRACTS=(
"ILegacyMintableERC20" "ILegacyMintableERC20"
"IOptimismMintableERC20" "IOptimismMintableERC20"
"IOptimismMintableERC721" "IOptimismMintableERC721"
"IOptimismSuperchainERC20"
# Doesn't start with "I" # Doesn't start with "I"
"MintableAndBurnable"
"KontrolCheatsBase" "KontrolCheatsBase"
# Currently inherit from interface, needs to be fixed. # Currently inherit from interface, needs to be fixed.
"IWETH" "IWETH"
"IDelayedWETH" "IDelayedWETH"
"ISuperchainWETH"
"IL2ToL2CrossDomainMessenger" "IL2ToL2CrossDomainMessenger"
"ICrossL2Inbox" "ICrossL2Inbox"
"ISystemConfigInterop" "ISystemConfigInterop"
......
...@@ -27,9 +27,8 @@ var excludeContracts = []string{ ...@@ -27,9 +27,8 @@ var excludeContracts = []string{
// TODO: Interfaces that need to be fixed // TODO: Interfaces that need to be fixed
"IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20", "IInitializable", "IPreimageOracle", "ILegacyMintableERC20", "IOptimismMintableERC20",
"IOptimismMintableERC721", "IOptimismSuperchainERC20", "MintableAndBurnable", "IOptimismMintableERC721", "KontrolCheatsBase", "IWETH", "IDelayedWETH", "ISuperchainWETH",
"KontrolCheatsBase", "IWETH", "IDelayedWETH", "IL2ToL2CrossDomainMessenger", "IL2ToL2CrossDomainMessenger", "ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy",
"ICrossL2Inbox", "ISystemConfigInterop", "IResolvedDelegateProxy",
} }
type ContractDefinition struct { type ContractDefinition struct {
......
...@@ -129,9 +129,14 @@ func run() error { ...@@ -129,9 +129,14 @@ func run() error {
return return
} }
// Skip mock contracts
if strings.HasPrefix(contractName, "Mock") {
return
}
contractPath := contractFiles[contractName] contractPath := contractFiles[contractName]
if contractPath == "" { 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 return
} }
......
...@@ -96,8 +96,8 @@ ...@@ -96,8 +96,8 @@
"sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84" "sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84"
}, },
"src/L2/L2StandardBridgeInterop.sol": { "src/L2/L2StandardBridgeInterop.sol": {
"initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef", "initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6",
"sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994" "sourceCodeHash": "0x36087332a4365ee172eed8fa35aa48e804b08279e97332058a588c2d4ae30c6b"
}, },
"src/L2/L2ToL1MessagePasser.sol": { "src/L2/L2ToL1MessagePasser.sol": {
"initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d",
...@@ -108,24 +108,32 @@ ...@@ -108,24 +108,32 @@
"sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239"
}, },
"src/L2/OptimismSuperchainERC20.sol": { "src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0x965af580568bad2b47d04c6ea536490aa263e9fcb5fb43e6c8bc00929fda3df5", "initCodeHash": "0xd5c84e45746fd741d541a917ddc1cc0c7043c6b21d5c18040d4bc999d6a7b2db",
"sourceCodeHash": "0x9de349519900b1051f45d507b2fac1cf3f3ae8e2cfb1ceb56875a7ace1cb6ab8" "sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a"
}, },
"src/L2/OptimismSuperchainERC20Beacon.sol": { "src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7",
"sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e" "sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e"
}, },
"src/L2/OptimismSuperchainERC20Factory.sol": { "src/L2/OptimismSuperchainERC20Factory.sol": {
"initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", "initCodeHash": "0x18a362c57f08b611db98dfde96121385e938f995c84e3547c1c03fd49f9db2fd",
"sourceCodeHash": "0x1e02d78a4e7ee93a07f7af7a78fe1773d0e87711f23a4ccd10a8692b47644a34" "sourceCodeHash": "0x450cd89d0aae7bbc85ff57a14a6d3468c24c6743f25943f6d895d34b1456c456"
}, },
"src/L2/SequencerFeeVault.sol": { "src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0xcaadbf08057b5d47f7704257e9385a29e42a7a08c818646d109c5952d3d35218", "initCodeHash": "0xcaadbf08057b5d47f7704257e9385a29e42a7a08c818646d109c5952d3d35218",
"sourceCodeHash": "0x05bbc6039e5a9ff38987e7b9b89c69e2ee8aa4b7ca20dd002ea1bbd3d70f27f3" "sourceCodeHash": "0x05bbc6039e5a9ff38987e7b9b89c69e2ee8aa4b7ca20dd002ea1bbd3d70f27f3"
}, },
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0x75d061633a141af11a19b86e599a1725dfae8d245dcddfb6bb244a50d5e53f96"
},
"src/L2/SuperchainTokenBridge.sol": {
"initCodeHash": "0x07fc1d495928d9c13bd945a049d17e1d105d01c2082a7719e5d18cbc0e1c7d9e",
"sourceCodeHash": "0xaf2458e48dcadcafa8940cde7368549eae2280eef91247600d864ddac20f5d82"
},
"src/L2/SuperchainWETH.sol": { "src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x4ccd25f37a816205bc26f8532afa66e02f2b36ca7b7404d0fa48a4313ed16f0c", "initCodeHash": "0x50f6ea9bfe650fcf792e98e44b1bf66c036fd0e6d4b753da680253d7d8609816",
"sourceCodeHash": "0xd186614f1515fa3ba2f43e401e639bfa3159603954e39a51769e9b57ad19a3fd" "sourceCodeHash": "0x82d03262decf52d5954d40bca8703f96a0f3ba7accf6c1d75292856c2f34cf8f"
}, },
"src/L2/WETH.sol": { "src/L2/WETH.sol": {
"initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956",
......
...@@ -102,6 +102,42 @@ ...@@ -102,6 +102,42 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "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": [], "inputs": [],
"name": "decimals", "name": "decimals",
...@@ -236,29 +272,6 @@ ...@@ -236,29 +272,6 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "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": [], "inputs": [],
"name": "remoteToken", "name": "remoteToken",
...@@ -272,29 +285,6 @@ ...@@ -272,29 +285,6 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "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": [ "inputs": [
{ {
...@@ -437,7 +427,7 @@ ...@@ -437,7 +427,7 @@
{ {
"indexed": true, "indexed": true,
"internalType": "address", "internalType": "address",
"name": "account", "name": "from",
"type": "address" "type": "address"
}, },
{ {
...@@ -450,26 +440,13 @@ ...@@ -450,26 +440,13 @@
"name": "Burn", "name": "Burn",
"type": "event" "type": "event"
}, },
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "version",
"type": "uint64"
}
],
"name": "Initialized",
"type": "event"
},
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
{ {
"indexed": true, "indexed": true,
"internalType": "address", "internalType": "address",
"name": "account", "name": "from",
"type": "address" "type": "address"
}, },
{ {
...@@ -479,18 +456,12 @@ ...@@ -479,18 +456,12 @@
"type": "uint256" "type": "uint256"
} }
], ],
"name": "Mint", "name": "CrosschainBurnt",
"type": "event" "type": "event"
}, },
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{ {
"indexed": true, "indexed": true,
"internalType": "address", "internalType": "address",
...@@ -502,26 +473,27 @@ ...@@ -502,26 +473,27 @@
"internalType": "uint256", "internalType": "uint256",
"name": "amount", "name": "amount",
"type": "uint256" "type": "uint256"
}
],
"name": "CrosschainMinted",
"type": "event"
}, },
{
"anonymous": false,
"inputs": [
{ {
"indexed": false, "indexed": false,
"internalType": "uint256", "internalType": "uint64",
"name": "source", "name": "version",
"type": "uint256" "type": "uint64"
} }
], ],
"name": "RelayERC20", "name": "Initialized",
"type": "event" "type": "event"
}, },
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{ {
"indexed": true, "indexed": true,
"internalType": "address", "internalType": "address",
...@@ -533,15 +505,9 @@ ...@@ -533,15 +505,9 @@
"internalType": "uint256", "internalType": "uint256",
"name": "amount", "name": "amount",
"type": "uint256" "type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "destination",
"type": "uint256"
} }
], ],
"name": "SendERC20", "name": "Mint",
"type": "event" "type": "event"
}, },
{ {
...@@ -579,11 +545,6 @@ ...@@ -579,11 +545,6 @@
"name": "AllowanceUnderflow", "name": "AllowanceUnderflow",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "CallerNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "InsufficientAllowance", "name": "InsufficientAllowance",
...@@ -594,11 +555,6 @@ ...@@ -594,11 +555,6 @@
"name": "InsufficientBalance", "name": "InsufficientBalance",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "InvalidInitialization", "name": "InvalidInitialization",
...@@ -616,17 +572,17 @@ ...@@ -616,17 +572,17 @@
}, },
{ {
"inputs": [], "inputs": [],
"name": "OnlyBridge", "name": "PermitExpired",
"type": "error" "type": "error"
}, },
{ {
"inputs": [], "inputs": [],
"name": "PermitExpired", "name": "TotalSupplyOverflow",
"type": "error" "type": "error"
}, },
{ {
"inputs": [], "inputs": [],
"name": "TotalSupplyOverflow", "name": "Unauthorized",
"type": "error" "type": "error"
}, },
{ {
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
"inputs": [ "inputs": [
{ {
"internalType": "address", "internalType": "address",
"name": "_superchainToken", "name": "_localToken",
"type": "address" "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 @@ ...@@ -410,12 +410,17 @@
}, },
{ {
"inputs": [], "inputs": [],
"name": "NotCustomGasToken", "name": "CallerNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error" "type": "error"
}, },
{ {
"inputs": [], "inputs": [],
"name": "Unauthorized", "name": "NotCustomGasToken",
"type": "error" "type": "error"
} }
] ]
\ No newline at end of file
...@@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; ...@@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.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. /// @notice Thrown when the decimals of the tokens are not the same.
error InvalidDecimals(); error InvalidDecimals();
...@@ -18,26 +19,18 @@ error InvalidDecimals(); ...@@ -18,26 +19,18 @@ error InvalidDecimals();
/// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory.
error InvalidLegacyERC20Address(); 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(); error InvalidSuperchainERC20Address();
/// @notice Thrown when the remote addresses of the tokens are not the same. /// @notice Thrown when the remote addresses of the tokens are not the same.
error InvalidTokenPair(); 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:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @custom:predeploy 0x4200000000000000000000000000000000000010
/// @title L2StandardBridgeInterop /// @title L2StandardBridgeInterop
/// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for
/// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20)
/// and SuperchainERC20 tokens. /// and OptimismSuperchainERC20 tokens.
contract L2StandardBridgeInterop is L2StandardBridge { contract L2StandardBridgeInterop is L2StandardBridge {
/// @notice Emitted when a conversion is made. /// @notice Emitted when a conversion is made.
/// @param from The token being converted from. /// @param from The token being converted from.
...@@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { ...@@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge {
event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount);
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver +interop /// @custom:semver +interop-beta.1
function version() public pure override returns (string memory) { 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. /// @notice Converts `amount` of `from` token to `to` token.
...@@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { ...@@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge {
function convert(address _from, address _to, uint256 _amount) external { function convert(address _from, address _to, uint256 _amount) external {
_validatePair(_from, _to); _validatePair(_from, _to);
MintableAndBurnable(_from).burn(msg.sender, _amount); IMintableAndBurnableERC20(_from).burn(msg.sender, _amount);
MintableAndBurnable(_to).mint(msg.sender, _amount); IMintableAndBurnableERC20(_to).mint(msg.sender, _amount);
emit Converted(_from, _to, msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount);
} }
...@@ -82,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { ...@@ -82,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge {
/// @notice Validates that the tokens are deployed by the correct factory. /// @notice Validates that the tokens are deployed by the correct factory.
/// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). /// @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 { function _validateFactories(address _legacyAddr, address _superAddr) internal view {
// 2. Valid legacy check // 2. Valid legacy check
address _legacyRemoteToken = address _legacyRemoteToken =
IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr);
if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address();
// 3. Valid SuperchainERC20 check // 3. Valid OptimismSuperchainERC20 check
address _superRemoteToken = address _superRemoteToken =
IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr);
if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address();
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.25; pragma solidity 0.8.25;
import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.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 { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol";
import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol";
/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge.
error OnlyBridge();
/// @custom:proxied true /// @custom:proxied true
/// @title OptimismSuperchainERC20 /// @title OptimismSuperchainERC20
/// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// @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 /// 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 /// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a
/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse /// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it
/// conversion path. /// also enables the inverse conversion path.
/// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding.
contract OptimismSuperchainERC20 is contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 {
IOptimismSuperchainERC20Extension, /// @notice Emitted whenever tokens are minted for an account.
SuperchainERC20, /// @param to Address of the account tokens are being minted for.
ISemver, /// @param amount Amount of tokens minted.
Initializable, event Mint(address indexed to, uint256 amount);
ERC165
{ /// @notice Emitted whenever tokens are burned from an account.
/// @notice Address of the StandardBridge Predeploy. /// @param from Address of the account tokens are being burned from.
address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; /// @param amount Amount of tokens burned.
event Burn(address indexed from, uint256 amount);
/// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at.
/// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff));
...@@ -55,15 +52,15 @@ contract OptimismSuperchainERC20 is ...@@ -55,15 +52,15 @@ contract OptimismSuperchainERC20 is
} }
} }
/// @notice A modifier that only allows the bridge to call /// @notice A modifier that only allows the L2StandardBridge to call
modifier onlyBridge() { modifier onlyL2StandardBridge() {
if (msg.sender != BRIDGE) revert OnlyBridge(); if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) revert Unauthorized();
_; _;
} }
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.5 /// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.5"; string public constant override version = "1.0.0-beta.6";
/// @notice Constructs the OptimismSuperchainERC20 contract. /// @notice Constructs the OptimismSuperchainERC20 contract.
constructor() { constructor() {
...@@ -94,7 +91,7 @@ contract OptimismSuperchainERC20 is ...@@ -94,7 +91,7 @@ contract OptimismSuperchainERC20 is
/// @notice Allows the L2StandardBridge to mint tokens. /// @notice Allows the L2StandardBridge to mint tokens.
/// @param _to Address to mint tokens to. /// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint. /// @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(); if (_to == address(0)) revert ZeroAddress();
_mint(_to, _amount); _mint(_to, _amount);
...@@ -105,7 +102,7 @@ contract OptimismSuperchainERC20 is ...@@ -105,7 +102,7 @@ contract OptimismSuperchainERC20 is
/// @notice Allows the L2StandardBridge to burn tokens. /// @notice Allows the L2StandardBridge to burn tokens.
/// @param _from Address to burn tokens from. /// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn. /// @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(); if (_from == address(0)) revert ZeroAddress();
_burn(_from, _amount); _burn(_from, _amount);
...@@ -114,7 +111,7 @@ contract OptimismSuperchainERC20 is ...@@ -114,7 +111,7 @@ contract OptimismSuperchainERC20 is
} }
/// @notice Returns the address of the corresponding version of this token on the remote chain. /// @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; return _getStorage().remoteToken;
} }
...@@ -142,7 +139,6 @@ contract OptimismSuperchainERC20 is ...@@ -142,7 +139,6 @@ contract OptimismSuperchainERC20 is
/// @param _interfaceId Interface ID to check. /// @param _interfaceId Interface ID to check.
/// @return Whether or not the interface is supported by this contract. /// @return Whether or not the interface is supported by this contract.
function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
return return _interfaceId == type(IOptimismSuperchainERC20).interfaceId || super.supportsInterface(_interfaceId);
_interfaceId == type(IOptimismSuperchainERC20Extension).interfaceId || super.supportsInterface(_interfaceId);
} }
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.25; pragma solidity 0.8.25;
import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
...@@ -13,13 +12,9 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; ...@@ -13,13 +12,9 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol";
/// @title OptimismSuperchainERC20Factory /// @title OptimismSuperchainERC20Factory
/// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies
/// using CREATE3. /// using CREATE3.
contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { contract OptimismSuperchainERC20Factory is 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;
/// @notice Emitted when an OptimismSuperchainERC20 is deployed. /// @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 remoteToken Address of the corresponding token on the remote chain.
/// @param deployer Address of the account that deployed the token. /// @param deployer Address of the account that deployed the token.
event OptimismSuperchainERC20Created( event OptimismSuperchainERC20Created(
...@@ -27,8 +22,12 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { ...@@ -27,8 +22,12 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
); );
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.3 /// @custom:semver 1.0.0-beta.4
string public constant version = "1.0.0-beta.3"; 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. /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3.
/// @param _remoteToken Address of the remote token. /// @param _remoteToken Address of the remote token.
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.25; pragma solidity 0.8.25;
import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { Predeploys } from "src/libraries/Predeploys.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 /// @title SuperchainERC20
/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// @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 /// bridging to make it fungible across the Superchain. This construction allows the SuperchainTokenBridge to
/// both replay protection and domain binding. /// burn and mint tokens.
abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ISuperchainERC20Errors, ERC20 { abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. /// @notice A modifier that only allows the SuperchainTokenBridge to call
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; modifier onlySuperchainTokenBridge() {
if (msg.sender != Predeploys.SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized();
/// @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); /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
function version() external view virtual returns (string memory) {
return "1.0.0-beta.1";
} }
/// @notice Relays tokens received from another chain. /// @notice Allows the SuperchainTokenBridge to mint tokens.
/// @param _from Address of the msg.sender of sendERC20 on the source chain. /// @param _to Address to mint tokens to.
/// @param _to Address to relay tokens to. /// @param _amount Amount of tokens to mint.
/// @param _amount Amount of tokens to relay. function crosschainMint(address _to, uint256 _amount) external onlySuperchainTokenBridge {
function relayERC20(address _from, address _to, uint256 _amount) external virtual { _mint(_to, _amount);
if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger();
if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { emit CrosschainMinted(_to, _amount);
revert InvalidCrossDomainSender();
} }
uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); /// @notice Allows the SuperchainTokenBridge to burn tokens.
/// @param _from Address to burn tokens from.
_mint(_to, _amount); /// @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; ...@@ -5,24 +5,25 @@ pragma solidity 0.8.15;
import { WETH98 } from "src/universal/WETH98.sol"; import { WETH98 } from "src/universal/WETH98.sol";
// Libraries // Libraries
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
// Interfaces // Interfaces
import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.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 { IL1Block } from "src/L2/interfaces/IL1Block.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.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 /// @title SuperchainWETH
/// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains /// @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 /// within the superchain. SuperchainWETH can be converted into native ETH on chains that
/// do not use a custom gas token. /// do not use a custom gas token.
contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { contract SuperchainWETH is WETH98, ISuperchainWETH, ISemver {
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.5 /// @custom:semver 1.0.0-beta.6
string public constant version = "1.0.0-beta.5"; string public constant version = "1.0.0-beta.6";
/// @inheritdoc WETH98 /// @inheritdoc WETH98
function deposit() public payable override { function deposit() public payable override {
...@@ -36,7 +37,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { ...@@ -36,7 +37,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
super.withdraw(wad); super.withdraw(wad);
} }
/// @inheritdoc ISuperchainERC20Extensions /// @inheritdoc ISuperchainWETH
function sendERC20(address dst, uint256 wad, uint256 chainId) public { function sendERC20(address dst, uint256 wad, uint256 chainId) public {
// Burn from user's balance. // Burn from user's balance.
_burn(msg.sender, wad); _burn(msg.sender, wad);
...@@ -57,12 +58,12 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { ...@@ -57,12 +58,12 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver {
emit SendERC20(msg.sender, dst, wad, chainId); emit SendERC20(msg.sender, dst, wad, chainId);
} }
/// @inheritdoc ISuperchainERC20Extensions /// @inheritdoc ISuperchainWETH
function relayERC20(address from, address dst, uint256 wad) external { function relayERC20(address from, address dst, uint256 wad) external {
// Receive message from other chain. // Receive message from other chain.
IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
if (msg.sender != address(messenger)) revert Unauthorized(); if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger();
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized(); if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
// Mint from ETHLiquidity contract. // Mint from ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { 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 // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol";
import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.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 { interface IL2StandardBridgeInterop is IStandardBridge {
error InvalidDecimals(); error InvalidDecimals();
error InvalidLegacyERC20Address(); 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 @@ ...@@ -2,38 +2,30 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
// Interfaces // Interfaces
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol";
/// @title IOptimismSuperchainERC20Extension /// @title IOptimismSuperchainERC20
/// @notice This interface is available on the OptimismSuperchainERC20 contract. /// @notice This interface is available on the OptimismSuperchainERC20 contract.
/// We declare it as a separate interface so that it can be used in interface IOptimismSuperchainERC20 is ISuperchainERC20 {
/// custom implementations of SuperchainERC20. error ZeroAddress();
interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors { error InvalidInitialization();
/// @notice Emitted whenever tokens are minted for an account. error NotInitializing();
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted. event Initialized(uint64 version);
event Mint(address indexed account, uint256 amount);
event Mint(address indexed to, uint256 amount);
/// @notice Emitted whenever tokens are burned from an account.
/// @param account Address of the account tokens are being burned from. event Burn(address indexed from, uint256 amount);
/// @param amount Amount of tokens burned.
event Burn(address indexed account, uint256 amount); function initialize(address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals) external;
/// @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; 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; 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); function remoteToken() external view returns (address);
}
/// @title IOptimismSuperchainERC20 function supportsInterface(bytes4 _interfaceId) external view returns (bool);
/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface.
interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } function __constructor__() external;
}
...@@ -11,8 +11,6 @@ interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { ...@@ -11,8 +11,6 @@ interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver {
address indexed superchainToken, address indexed remoteToken, address deployer 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( function deploy(
address _remoteToken, address _remoteToken,
string memory _name, string memory _name,
......
...@@ -2,57 +2,15 @@ ...@@ -2,57 +2,15 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
// Interfaces // Interfaces
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";
import { ISemver } from "src/universal/interfaces/ISemver.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();
}
/// @title ISuperchainERC20 /// @title ISuperchainERC20
/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface. /// @notice This interface is available on the SuperchainERC20 contract.
interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors { /// @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; 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 // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { IWETH } from "src/universal/interfaces/IWETH.sol";
interface ISuperchainWETH { interface ISuperchainWETH {
/// @notice Thrown when attempting a deposit or withdrawal and the chain uses a custom gas token.
error NotCustomGasToken(); error NotCustomGasToken();
error Unauthorized();
event Approval(address indexed src, address indexed guy, uint256 wad); /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
event Deposit(address indexed dst, uint256 wad); /// 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); 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 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); /// @notice Sends tokens to some target address on another chain.
/// @param _dst Address to send tokens to.
fallback() external payable; /// @param _wad Amount of tokens to send.
/// @param _chainId Chain ID of the destination chain.
receive() external payable; function sendERC20(address _dst, uint256 _wad, uint256 _chainId) external;
function allowance(address, address) external view returns (uint256); /// @notice Relays tokens received from another chain.
function approve(address guy, uint256 wad) external returns (bool); /// @param _from Address of the msg.sender of sendERC20 on the source chain.
function balanceOf(address) external view returns (uint256); /// @param _dst Address to relay tokens to.
function decimals() external view returns (uint8); /// @param _wad Amount of tokens to relay.
function deposit() external payable; function relayERC20(address _from, address _dst, uint256 _wad) external;
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;
} }
interface ISuperchainWETHERC20 is IWETH, ISuperchainWETH { }
...@@ -105,6 +105,9 @@ library Predeploys { ...@@ -105,6 +105,9 @@ library Predeploys {
/// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract.
address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; 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. /// @notice Returns the name of the predeploy at the given address.
function getName(address _addr) internal pure returns (string memory out_) { function getName(address _addr) internal pure returns (string memory out_) {
require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy");
...@@ -135,6 +138,7 @@ library Predeploys { ...@@ -135,6 +138,7 @@ library Predeploys {
if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; if (_addr == ETH_LIQUIDITY) return "ETHLiquidity";
if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory";
if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon";
if (_addr == SUPERCHAIN_TOKEN_BRIDGE) return "SuperchainTokenBridge";
revert("Predeploys: unnamed predeploy"); revert("Predeploys: unnamed predeploy");
} }
...@@ -154,7 +158,8 @@ library Predeploys { ...@@ -154,7 +158,8 @@ library Predeploys {
|| (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER)
|| (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY)
|| (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) || (_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) { function isPredeployNamespace(address _addr) internal pure returns (bool) {
......
...@@ -12,3 +12,6 @@ error NotCustomGasToken(); ...@@ -12,3 +12,6 @@ error NotCustomGasToken();
/// @notice Error for when a transfer via call fails. /// @notice Error for when a transfer via call fails.
error TransferFailed(); 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 { ...@@ -150,8 +150,8 @@ contract L2GenesisTest is Test {
// 2 predeploys do not have proxies // 2 predeploys do not have proxies
assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); 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 // 24 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); 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 // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin
assertEq( assertEq(
......
...@@ -5,7 +5,8 @@ pragma solidity 0.8.15; ...@@ -5,7 +5,8 @@ pragma solidity 0.8.15;
import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
// Interfaces // 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 { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol";
...@@ -53,9 +54,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { ...@@ -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 { 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 { function _setUpLegacyToSuper(address _from, address _to) internal {
// Assume // Assume
_assumeAddress(_from); _assumeAddress(_from);
...@@ -198,9 +199,11 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T ...@@ -198,9 +199,11 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T
// Mock and expect the `burn` and `mint` functions // Mock and expect the `burn` and `mint` functions
_mockAndExpect( _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 // Act
vm.prank(_caller); vm.prank(_caller);
...@@ -208,9 +211,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T ...@@ -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 { 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 { function _setUpSuperToLegacy(address _from, address _to) internal {
// Assume // Assume
_assumeAddress(_from); _assumeAddress(_from);
...@@ -354,9 +357,11 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T ...@@ -354,9 +357,11 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T
// Mock and expect the `burn` and `mint` functions // Mock and expect the `burn` and `mint` functions
_mockAndExpect( _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 // Act
vm.prank(_caller); vm.prank(_caller);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.25; pragma solidity 0.8.15;
// Testing utilities // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
// Libraries // Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol";
import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";
// Target contract // 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 /// @title OptimismSuperchainERC20FactoryTest
/// @notice Contract for testing the OptimismSuperchainERC20Factory contract. /// @notice Contract for testing the OptimismSuperchainERC20Factory contract.
contract OptimismSuperchainERC20FactoryTest is Test { contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer {
using Bytes32AddressLib for bytes32; using Bytes32AddressLib for bytes32;
OptimismSuperchainERC20 public superchainERC20Impl; event OptimismSuperchainERC20Created(
OptimismSuperchainERC20Factory public superchainERC20Factory; address indexed superchainToken, address indexed remoteToken, address deployer
);
/// @notice Sets up the test suite. /// @notice Sets up the test suite.
function setUp() public { function setUp() public override {
superchainERC20Impl = new OptimismSuperchainERC20(); super.enableInterop();
super.setUp();
// 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))
);
} }
/// @notice Test that calling `deploy` with valid parameters succeeds. /// @notice Test that calling `deploy` with valid parameters succeeds.
...@@ -62,22 +38,22 @@ contract OptimismSuperchainERC20FactoryTest is Test { ...@@ -62,22 +38,22 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{ {
// Arrange // Arrange
bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); 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)); vm.expectEmit(address(l2OptimismSuperchainERC20Factory));
emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller);
// Act // Act
vm.prank(_caller); vm.prank(_caller);
address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
// Assert // Assert
assertTrue(addr == deployment); assertTrue(addr == deployment);
assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); assertTrue(IERC20Metadata(deployment).decimals() == _decimals);
assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken);
assertEq(OptimismSuperchainERC20(deployment).name(), _name); assertEq(IERC20Metadata(deployment).name(), _name);
assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); assertEq(IERC20Metadata(deployment).symbol(), _symbol);
assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken);
} }
/// @notice Test that calling `deploy` with the same parameters twice reverts. /// @notice Test that calling `deploy` with the same parameters twice reverts.
...@@ -92,13 +68,13 @@ contract OptimismSuperchainERC20FactoryTest is Test { ...@@ -92,13 +68,13 @@ contract OptimismSuperchainERC20FactoryTest is Test {
{ {
// Arrange // Arrange
vm.prank(_caller); vm.prank(_caller);
superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals);
vm.expectRevert(bytes("DEPLOYMENT_FAILED")); vm.expectRevert(bytes("DEPLOYMENT_FAILED"));
// Act // Act
vm.prank(_caller); 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. /// @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"; ...@@ -6,11 +6,12 @@ import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries // Libraries
import { Predeploys } from "src/libraries/Predeploys.sol"; 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 // Interfaces
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
/// @title SuperchainWETH_Test /// @title SuperchainWETH_Test
/// @notice Contract for testing the SuperchainWETH contract. /// @notice Contract for testing the SuperchainWETH contract.
...@@ -320,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest { ...@@ -320,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest {
// Nothing to arrange. // Nothing to arrange.
// Act // Act
vm.expectRevert(Unauthorized.selector); vm.expectRevert(ISuperchainWETH.CallerNotL2ToL2CrossDomainMessenger.selector);
vm.prank(alice); vm.prank(alice);
superchainWeth.relayERC20(_sender, bob, _amount); superchainWeth.relayERC20(_sender, bob, _amount);
...@@ -345,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest { ...@@ -345,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest {
); );
// Act // Act
vm.expectRevert(Unauthorized.selector); vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector);
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainWeth.relayERC20(_sender, bob, _amount); superchainWeth.relayERC20(_sender, bob, _amount);
......
...@@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol"; ...@@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol";
// Libraries // Libraries
import { Predeploys } from "src/libraries/Predeploys.sol"; 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 { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol";
import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol";
import { HandlerGetters } from "./helpers/HandlerGetters.t.sol"; import { HandlerGetters } from "./helpers/HandlerGetters.t.sol";
...@@ -38,7 +38,7 @@ contract OptimismSuperchainERC20Properties is Test { ...@@ -38,7 +38,7 @@ contract OptimismSuperchainERC20Properties is Test {
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) { if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); totalSupply += SuperchainERC20(supertoken).totalSupply();
} }
} }
assertEq(trackedSupply, totalSupply + fundsInTransit); assertEq(trackedSupply, totalSupply + fundsInTransit);
...@@ -61,7 +61,7 @@ contract OptimismSuperchainERC20Properties is Test { ...@@ -61,7 +61,7 @@ contract OptimismSuperchainERC20Properties is Test {
for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) {
address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt);
if (supertoken != address(0)) { if (supertoken != address(0)) {
totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); totalSupply += SuperchainERC20(supertoken).totalSupply();
} }
} }
assertEq(trackedSupply, totalSupply); assertEq(trackedSupply, totalSupply);
...@@ -69,7 +69,7 @@ contract OptimismSuperchainERC20Properties is Test { ...@@ -69,7 +69,7 @@ contract OptimismSuperchainERC20Properties is Test {
} }
/// @custom:invariant many other assertion mode invariants are also defined under /// @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 /// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the
/// handler for assertion test failures /// handler for assertion test failures
......
# Supertoken advanced testing # 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 ## Milestones
The supertoken ecosystem consists of not just the supertoken contract, but the required changes to other contracts for liquidity to reach the former. 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 ...@@ -12,8 +16,8 @@ Considering only the supertoken contract is merged into the `develop` branch, an
## Definitions ## 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. - _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` - _supertoken:_ a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory`
# Ecosystem properties # Ecosystem properties
...@@ -28,7 +32,7 @@ legend: ...@@ -28,7 +32,7 @@ legend:
## Unit test ## Unit test
| id | milestone | description | tested | | id | milestone | description | tested |
| --- | --- | --- | --- | | --- | ------------------- | ------------------------------------------------------------------------------------------ | ------ |
| 0 | Factories | supertoken token address does not depend on the executing chain’s chainID | [ ] | | 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 | [ ] | | 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 | [ ] | | 2 | Liquidity Migration | convert() should only allow converting legacy tokens to supertoken and viceversa | [ ] |
...@@ -40,18 +44,18 @@ legend: ...@@ -40,18 +44,18 @@ legend:
## Valid state ## Valid state
| id | milestone | description | tested | | id | milestone | description | tested |
| --- | --- | --- | --- | | --- | --------- | ------------------------------------------------------------------------------ | ------ |
| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [x] | | 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 | [~] | | 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [] |
## Variable transition ## Variable transition
| id | milestone | description | tested | | id | milestone | description | tested |
| --- | --- | --- | --- | | --- | ------------------- | ------------------------------------------------------------------------------------------------- | ------ |
| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [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 | [x] | | 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 | [x] | | 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 | [x] | | 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] | | 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 | [ ] | | 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 | [~] | | 12 | Liquidity Migration | supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge | [~] |
...@@ -63,7 +67,7 @@ legend: ...@@ -63,7 +67,7 @@ legend:
## High level ## High level
| id | milestone | description | tested | | id | milestone | description | tested |
| --- | --- | --- | --- | | --- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase 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 | [ ] | | 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) | [~] | | 19 | Liquidity Migration | sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy) | [~] |
...@@ -76,7 +80,7 @@ As another layer of defense, the following properties are defined which assume b ...@@ -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 It’s worth noting that these properties will not hold for a live system
| id | milestone | description | tested | | 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] | | 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 | [x] | | 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) | [~] | | 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 @@ ...@@ -2,7 +2,7 @@
pragma solidity ^0.8.25; pragma solidity ^0.8.25;
import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; 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 { ProtocolHandler } from "../handlers/Protocol.t.sol";
import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol";
...@@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { ...@@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
validateTokenDeployParams(params) validateTokenDeployParams(params)
{ {
chainId = bound(chainId, 0, MAX_CHAINS - 1); chainId = bound(chainId, 0, MAX_CHAINS - 1);
OptimismSuperchainERC20 supertoken = _deploySupertoken( SuperchainERC20 supertoken = _deploySupertoken(
remoteTokens[params.remoteTokenIndex], remoteTokens[params.remoteTokenIndex],
WORDS[params.nameIndex], WORDS[params.nameIndex],
WORDS[params.symbolIndex], WORDS[params.symbolIndex],
...@@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { ...@@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
compatibleAssert(supertoken.totalSupply() == 0); 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-id 11
/// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input /// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input
/// amount /// amount
...@@ -135,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { ...@@ -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 /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid
function fuzz_relayERC20(uint256 messageIndex) external { function fuzz_relayERC20(uint256 messageIndex) external {
MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex); MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex);
OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender); SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender);
uint256 destinationSupplyBefore = destinationToken.totalSupply(); uint256 destinationSupplyBefore = destinationToken.totalSupply();
uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient); uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient);
...@@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { ...@@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert {
compatibleAssert(false); 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"; ...@@ -10,78 +10,6 @@ import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol";
contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
using EnumerableMap for EnumerableMap.Bytes32ToUintMap; 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-id 12
/// @custom:property supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge /// @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 { function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external {
...@@ -89,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { ...@@ -89,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); bytes32 salt = MESSENGER.superTokenInitDeploySalts(token);
amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply());
vm.prank(sender); vm.prank(sender);
try OptimismSuperchainERC20(token).mint(to, amount) { try OptimismSuperchainERC20(token).crosschainMint(to, amount) {
compatibleAssert(sender == BRIDGE); compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount);
...@@ -105,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { ...@@ -105,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert {
bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); bytes32 salt = MESSENGER.superTokenInitDeploySalts(token);
uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender);
vm.prank(sender); vm.prank(sender);
try OptimismSuperchainERC20(token).burn(from, amount) { try OptimismSuperchainERC20(token).crosschainBurn(from, amount) {
compatibleAssert(sender == BRIDGE); compatibleAssert(sender == BRIDGE);
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt);
ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount);
......
...@@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { ...@@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors {
index = bound(index, 0, allSuperTokens.length - 1); index = bound(index, 0, allSuperTokens.length - 1);
address addr = allSuperTokens[index]; address addr = allSuperTokens[index];
vm.prank(BRIDGE); vm.prank(BRIDGE);
OptimismSuperchainERC20(addr).mint(currentActor(), amount); OptimismSuperchainERC20(addr).crosschainMint(currentActor(), amount);
// currentValue will be zero if key is not present // currentValue will be zero if key is not present
(, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr));
ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount);
...@@ -164,7 +164,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { ...@@ -164,7 +164,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors {
bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId));
supertoken = OptimismSuperchainERC20( supertoken = OptimismSuperchainERC20(
address( address(
// TODO: Use the SuperchainERC20 Beacon Proxy // TODO: Use the OptimismSuperchainERC20 Beacon Proxy
new ERC1967Proxy{ salt: hackySalt }( new ERC1967Proxy{ salt: hackySalt }(
address(superchainERC20Impl), address(superchainERC20Impl),
abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals))
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.25; 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"; import { SafeCall } from "src/libraries/SafeCall.sol";
contract MockL2ToL2CrossDomainMessenger { contract MockL2ToL2CrossDomainMessenger {
...@@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger { ...@@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger {
) )
external external
view view
returns (OptimismSuperchainERC20) returns (SuperchainERC20)
{ {
return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]);
} }
function setCrossDomainMessageSender(address sender) external { function setCrossDomainMessageSender(address sender) external {
......
...@@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; ...@@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
// Interfaces // 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"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
/// @title SuperchainWETH_User /// @title SuperchainWETH_User
...@@ -29,7 +29,7 @@ contract SuperchainWETH_User is StdUtils { ...@@ -29,7 +29,7 @@ contract SuperchainWETH_User is StdUtils {
Vm internal vm; Vm internal vm;
/// @notice The SuperchainWETH contract. /// @notice The SuperchainWETH contract.
ISuperchainWETH internal weth; ISuperchainWETHERC20 internal weth;
/// @notice Mapping of sent messages. /// @notice Mapping of sent messages.
mapping(bytes32 => bool) internal sent; mapping(bytes32 => bool) internal sent;
...@@ -40,7 +40,7 @@ contract SuperchainWETH_User is StdUtils { ...@@ -40,7 +40,7 @@ contract SuperchainWETH_User is StdUtils {
/// @param _vm The Vm contract. /// @param _vm The Vm contract.
/// @param _weth The SuperchainWETH contract. /// @param _weth The SuperchainWETH contract.
/// @param _balance The initial balance of the 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; vm = _vm;
weth = _weth; weth = _weth;
vm.deal(address(this), _balance); 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 ...@@ -37,17 +37,18 @@ import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol
import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol";
import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol";
import { IAddressManager } from "src/legacy/interfaces/IAddressManager.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 { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol";
import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol";
import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol";
import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol";
import { IL1Block } from "src/L2/interfaces/IL1Block.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 { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { IWETH } from "src/universal/interfaces/IWETH.sol"; import { IWETH } from "src/universal/interfaces/IWETH.sol";
import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol";
import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol"; import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol";
import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge.sol";
/// @title Setup /// @title Setup
/// @dev This contact is responsible for setting up the contracts in state. It currently /// @dev This contact is responsible for setting up the contracts in state. It currently
...@@ -105,12 +106,11 @@ contract Setup { ...@@ -105,12 +106,11 @@ contract Setup {
IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN); IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN);
ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER); ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER);
IWETH weth = IWETH(payable(Predeploys.WETH)); 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); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY);
ISuperchainTokenBridge superchainTokenBridge = ISuperchainTokenBridge(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
// TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory =
IOptimismERC20Factory l2OptimismSuperchainERC20Factory = IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
/// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// @dev Deploys the Deploy contract without including its bytecode in the bytecode
/// of this contract by fetching the bytecode dynamically using `vm.getCode()`. /// of this contract by fetching the bytecode dynamically using `vm.getCode()`.
...@@ -236,6 +236,7 @@ contract Setup { ...@@ -236,6 +236,7 @@ contract Setup {
labelPredeploy(Predeploys.ETH_LIQUIDITY); labelPredeploy(Predeploys.ETH_LIQUIDITY);
labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY);
labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON);
labelPredeploy(Predeploys.SUPERCHAIN_TOKEN_BRIDGE);
// L2 Preinstalls // L2 Preinstalls
labelPreinstall(Preinstalls.MultiCall3); 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