diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 8e43cb748941..4c8038a0ac68 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -9,9 +9,9 @@ GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382 GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512723) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68357) -GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68921) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312) +GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file diff --git a/packages/contracts-bedrock/lib/forge-std b/packages/contracts-bedrock/lib/forge-std index 8f24d6b04c92..2d8b7b876a5b 160000 --- a/packages/contracts-bedrock/lib/forge-std +++ b/packages/contracts-bedrock/lib/forge-std @@ -1 +1 @@ -Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa +Subproject commit 2d8b7b876a5b328d6a73e13c4740ed7a0d72d5f4 diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index bfac0c367bfc..fc7e3d8a1503 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -158,6 +158,8 @@ abstract contract Artifacts { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } else if (digest == keccak256(bytes("SuperchainERC20Bridge"))) { + return payable(Predeploys.SUPERCHAIN_ERC20_BRIDGE); } return payable(address(0)); } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index ae4773a2eebd..71e7239241da 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -281,6 +281,7 @@ contract L2Genesis is Deployer { setETHLiquidity(); // 25 setOptimismSuperchainERC20Factory(); // 26 setOptimismSuperchainERC20Beacon(); // 27 + setSuperchainERC20Bridge(); // 28 } } @@ -555,6 +556,12 @@ contract L2Genesis is Deployer { vm.resetNonce(address(beacon)); } + /// @notice This predeploy is following the safety invariant #1. + /// This contract has no initializer. + function setSuperchainERC20Bridge() internal { + _setImplementationCode(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + } + /// @notice Sets all the preinstalls. function setPreinstalls() public { address tmpSetPreinstalls = address(uint160(uint256(keccak256("SetPreinstalls")))); diff --git a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go index d1e2153c02ef..cc65480d2c00 100644 --- a/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go +++ b/packages/contracts-bedrock/scripts/checks/semver-natspec/main.go @@ -129,9 +129,14 @@ func run() error { return } + // Skip mock contracts + if strings.Contains(contractName, "_MockContract") { + return + } + contractPath := contractFiles[contractName] if contractPath == "" { - fail("%s: Source file not found", contractName) + fail("%s: Source file not found (For test mock contracts, suffix the name with '_MockContract' to ignore this warning)", contractName) return } diff --git a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol index 5a7b48847614..e19cd7e994bd 100644 --- a/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol +++ b/packages/contracts-bedrock/scripts/ops/FeeVaultWithdrawal.s.sol @@ -65,7 +65,7 @@ contract FeeVaultWithdrawal is Script { } /// @notice Logs the information relevant to the user. - function log(uint256 _balance, address _recipient, address _vault) internal pure { + function log(uint256 _balance, address _recipient, address _vault) internal view { string memory logline = string.concat( "Withdrawing ", vm.toString(_balance), " to ", vm.toString(_recipient), " from ", vm.toString(_vault) ); diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index dc5c466bcf64..adae1ff092bf 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -104,8 +104,8 @@ "sourceCodeHash": "0xb55e58b5d4912edf05026878a5f5ac8019372212ed2a77843775d595fbf51b84" }, "src/L2/L2StandardBridgeInterop.sol": { - "initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef", - "sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994" + "initCodeHash": "0xd43d07c2ba5a73af56181c0221c28f3b495851b03cf8e35f9b009857bb9538a6", + "sourceCodeHash": "0x36087332a4365ee172eed8fa35aa48e804b08279e97332058a588c2d4ae30c6b" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", @@ -116,24 +116,32 @@ "sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xe3dbb0851669708901a4c6bb7ad7d55f9896deeec02cbe53ac58d689ff95b88b", - "sourceCodeHash": "0xe853817da47d32b4ec5bb5392405278c82a1e9620aef377491dcb371fbbe682f" + "initCodeHash": "0xc6452d9aef6d76bdc789f3cddac6862658a481c619e6a2e7a74f6d61147f927b", + "sourceCodeHash": "0x4463e49c98ceb3327bd768579341d1e0863c8c3925d4b533fbc0f7951306261f" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7", "sourceCodeHash": "0x5e58b7c867fafa49fe39d68d83875425e9cf94f05f2835bdcdaa08fc8bc6b68e" }, "src/L2/OptimismSuperchainERC20Factory.sol": { - "initCodeHash": "0x524bc58927ca60ba2fbc4b036ad00c5055758d5c5b2ebb3d75cb9b996175f2cb", - "sourceCodeHash": "0x155a4b22ff8e266560d1fae72e1db7fc164afd84b8a81afb74c69414e0d5438e" + "initCodeHash": "0x43ec413140b05bfb83ec453b0d4f82b33a2d560bf8c76405d08de17565b87053", + "sourceCodeHash": "0x04a88ee6c4cf68becf8727b53cbc56ab6cfbaac9dbeb61083f63613dbf823a76" }, "src/L2/SequencerFeeVault.sol": { "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", "sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a" }, + "src/L2/SuperchainERC20.sol": { + "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "sourceCodeHash": "0x9bc2e208774eb923894dbe391a5038a6189d7d36c202f4bf3e2c4dd332b0adf0" + }, + "src/L2/SuperchainERC20Bridge.sol": { + "initCodeHash": "0xea7eb314f96cd2520a58012ff7cc376c82c5a95612187ff6bb96ace4f095ebc4", + "sourceCodeHash": "0x83188d878ce0b2890a7f7f41d09a8807f94a126e0ea274f0dac8b93f77217d3b" + }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0x4ccd25f37a816205bc26f8532afa66e02f2b36ca7b7404d0fa48a4313ed16f0c", - "sourceCodeHash": "0xd186614f1515fa3ba2f43e401e639bfa3159603954e39a51769e9b57ad19a3fd" + "initCodeHash": "0x5db03c5c4cd6ea9e4b3e74e28f50d04fd3e130af5109b34fa208808fa9ba7742", + "sourceCodeHash": "0xdafbb056dbc6198ade27a0ee051e9cd1c8f03084beb50821dc93c82d710ef2b4" }, "src/L2/WETH.sol": { "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json index 6eb57764a8cb..7c24b3fe0065 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismSuperchainERC20.json @@ -17,6 +17,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "__superchainMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -236,29 +272,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "relayERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "remoteToken", @@ -272,29 +285,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_chainId", - "type": "uint256" - } - ], - "name": "sendERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -488,13 +478,7 @@ { "indexed": true, "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", + "name": "account", "type": "address" }, { @@ -502,15 +486,9 @@ "internalType": "uint256", "name": "amount", "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "source", - "type": "uint256" } ], - "name": "RelayERC20", + "name": "SuperchainBurnt", "type": "event" }, { @@ -519,13 +497,7 @@ { "indexed": true, "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", + "name": "account", "type": "address" }, { @@ -533,15 +505,9 @@ "internalType": "uint256", "name": "amount", "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "destination", - "type": "uint256" } ], - "name": "SendERC20", + "name": "SuperchainMinted", "type": "event" }, { @@ -579,11 +545,6 @@ "name": "AllowanceUnderflow", "type": "error" }, - { - "inputs": [], - "name": "CallerNotL2ToL2CrossDomainMessenger", - "type": "error" - }, { "inputs": [], "name": "InsufficientAllowance", @@ -596,27 +557,27 @@ }, { "inputs": [], - "name": "InvalidCrossDomainSender", + "name": "InvalidInitialization", "type": "error" }, { "inputs": [], - "name": "InvalidInitialization", + "name": "InvalidPermit", "type": "error" }, { "inputs": [], - "name": "InvalidPermit", + "name": "NotInitializing", "type": "error" }, { "inputs": [], - "name": "NotInitializing", + "name": "OnlyL2StandardBridge", "type": "error" }, { "inputs": [], - "name": "OnlyBridge", + "name": "OnlySuperchainERC20Bridge", "type": "error" }, { diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json new file mode 100644 index 000000000000..ed7ff2ba5129 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json @@ -0,0 +1,160 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "relayERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "name": "sendERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "source", + "type": "uint256" + } + ], + "name": "RelayERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destination", + "type": "uint256" + } + ], + "name": "SendERC20", + "type": "event" + }, + { + "inputs": [], + "name": "CallerNotL2ToL2CrossDomainMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index 600e0e6b64f7..b0b86ea7c8ce 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -410,12 +410,17 @@ }, { "inputs": [], - "name": "NotCustomGasToken", + "name": "CallerNotL2ToL2CrossDomainMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "NotCustomGasToken", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainERC20Bridge.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index be6c9e4c878d..eb25a406ede7 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -11,6 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; /// @notice Thrown when the decimals of the tokens are not the same. error InvalidDecimals(); @@ -18,26 +19,18 @@ error InvalidDecimals(); /// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. error InvalidLegacyERC20Address(); -/// @notice Thrown when the SuperchainERC20 address is not found in the SuperchainERC20Factory. +/// @notice Thrown when the OptimismSuperchainERC20 address is not found in the OptimismSuperchainERC20Factory. error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. error InvalidTokenPair(); -/// TODO: Define a better naming convention for this interface. -/// @notice Interface for minting and burning tokens in the L2StandardBridge. -/// Used for StandardL2ERC20, OptimismMintableERC20 and OptimismSuperchainERC20. -interface MintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} - /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @title L2StandardBridgeInterop /// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for /// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) -/// and SuperchainERC20 tokens. +/// and OptimismSuperchainERC20 tokens. contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Emitted when a conversion is made. /// @param from The token being converted from. @@ -47,9 +40,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop + /// @custom:semver +interop-beta.1 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop-beta.1"); } /// @notice Converts `amount` of `from` token to `to` token. @@ -59,8 +52,8 @@ contract L2StandardBridgeInterop is L2StandardBridge { function convert(address _from, address _to, uint256 _amount) external { _validatePair(_from, _to); - MintableAndBurnable(_from).burn(msg.sender, _amount); - MintableAndBurnable(_to).mint(msg.sender, _amount); + IMintableAndBurnableERC20(_from).burn(msg.sender, _amount); + IMintableAndBurnableERC20(_to).mint(msg.sender, _amount); emit Converted(_from, _to, msg.sender, _amount); } @@ -82,14 +75,14 @@ contract L2StandardBridgeInterop is L2StandardBridge { /// @notice Validates that the tokens are deployed by the correct factory. /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). - /// @param _superAddr The SuperchainERC20 address. + /// @param _superAddr The OptimismSuperchainERC20 address. function _validateFactories(address _legacyAddr, address _superAddr) internal view { // 2. Valid legacy check address _legacyRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); if (_legacyRemoteToken == address(0)) revert InvalidLegacyERC20Address(); - // 3. Valid SuperchainERC20 check + // 3. Valid OptimismSuperchainERC20 check address _superRemoteToken = IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); if (_superRemoteToken == address(0)) revert InvalidSuperchainERC20Address(); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index ffbec844e2ff..92616f72ac63 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -2,35 +2,20 @@ pragma solidity 0.8.25; import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; +import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; -import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; - -/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge. -error OnlyBridge(); /// @custom:proxied true /// @title OptimismSuperchainERC20 /// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token /// bridging to make it fungible across the Superchain. This construction allows the L2StandardBridge to burn -/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a SuperchainERC20 -/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse -/// conversion path. +/// and mint tokens. This makes it possible to convert a valid OptimismMintableERC20 token to a +/// OptimismSuperchainERC20 token, turning it fungible and interoperable across the superchain. Likewise, it +/// also enables the inverse conversion path. /// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding. -contract OptimismSuperchainERC20 is - IOptimismSuperchainERC20Extension, - SuperchainERC20, - ISemver, - Initializable, - ERC165 -{ - /// @notice Address of the StandardBridge Predeploy. - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; - +contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165, IOptimismSuperchainERC20Extension { /// @notice Storage slot that the OptimismSuperchainERC20Metadata struct is stored at. /// keccak256(abi.encode(uint256(keccak256("optimismSuperchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff)); bytes32 internal constant OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT = @@ -56,15 +41,17 @@ contract OptimismSuperchainERC20 is } } - /// @notice A modifier that only allows the bridge to call - modifier onlyBridge() { - if (msg.sender != BRIDGE) revert OnlyBridge(); + /// @notice A modifier that only allows the L2StandardBridge to call + modifier onlyL2StandardBridge() { + if (msg.sender != Predeploys.L2_STANDARD_BRIDGE) { + revert OnlyL2StandardBridge(); + } _; } /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.4 - string public constant version = "1.0.0-beta.4"; + /// @custom:semver 1.0.0-beta.6 + string public constant override version = "1.0.0-beta.6"; /// @notice Constructs the OptimismSuperchainERC20 contract. constructor() { @@ -95,7 +82,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. - function mint(address _to, uint256 _amount) external virtual onlyBridge { + function mint(address _to, uint256 _amount) external virtual onlyL2StandardBridge { if (_to == address(0)) revert ZeroAddress(); _mint(_to, _amount); @@ -106,7 +93,7 @@ contract OptimismSuperchainERC20 is /// @notice Allows the L2StandardBridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. - function burn(address _from, uint256 _amount) external virtual onlyBridge { + function burn(address _from, uint256 _amount) external virtual onlyL2StandardBridge { if (_from == address(0)) revert ZeroAddress(); _burn(_from, _amount); diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol index ffeb132e6cdf..e7ad7ed389b5 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20Factory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -13,13 +13,13 @@ import { CREATE3 } from "@rari-capital/solmate/src/utils/CREATE3.sol"; /// @title OptimismSuperchainERC20Factory /// @notice OptimismSuperchainERC20Factory is a factory contract that deploys OptimismSuperchainERC20 Beacon Proxies /// using CREATE3. -contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { +contract OptimismSuperchainERC20Factory is IOptimismSuperchainERC20Factory, 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. - /// @param superchainToken Address of the SuperchainERC20 deployment. + /// @param superchainToken Address of the OptimismSuperchainERC20 deployment. /// @param remoteToken Address of the corresponding token on the remote chain. /// @param deployer Address of the account that deployed the token. event OptimismSuperchainERC20Created( @@ -27,8 +27,8 @@ contract OptimismSuperchainERC20Factory is IOptimismERC20Factory, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.2 - string public constant version = "1.0.0-beta.2"; + /// @custom:semver 1.0.0-beta.3 + string public constant version = "1.0.0-beta.3"; /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. /// @param _remoteToken Address of the remote token. diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index e20b375ff891..6c48b231baaf 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,49 +1,43 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; -import { ERC20 } from "@solady/tokens/ERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import { ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; +import { ERC20 } from "@solady/tokens/ERC20.sol"; /// @title SuperchainERC20 /// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token -/// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for -/// both replay protection and domain binding. -abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ISuperchainERC20Errors, ERC20 { - /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. - address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external virtual { - if (_to == address(0)) revert ZeroAddress(); - - _burn(msg.sender, _amount); - - bytes memory _message = abi.encodeCall(this.relayERC20, (msg.sender, _to, _amount)); - IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message); - - emit SendERC20(msg.sender, _to, _amount, _chainId); +/// bridging to make it fungible across the Superchain. This construction allows the SuperchainERC20Bridge to +/// burn and mint tokens. +abstract contract SuperchainERC20 is ERC20, ISuperchainERC20Extension, ISemver { + /// @notice A modifier that only allows the SuperchainERC20Bridge to call + modifier onlySuperchainERC20Bridge() { + if (msg.sender != Predeploys.SUPERCHAIN_ERC20_BRIDGE) revert OnlySuperchainERC20Bridge(); + _; } - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external virtual { - if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + function version() external pure virtual returns (string memory) { + return "1.0.0-beta.1"; + } - if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { - revert InvalidCrossDomainSender(); - } + /// @notice Allows the SuperchainERC20Bridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function __superchainMint(address _to, uint256 _amount) external virtual onlySuperchainERC20Bridge { + _mint(_to, _amount); - uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + emit SuperchainMinted(_to, _amount); + } - _mint(_to, _amount); + /// @notice Allows the SuperchainERC20Bridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function __superchainBurn(address _from, uint256 _amount) external virtual onlySuperchainERC20Bridge { + _burn(_from, _amount); - emit RelayERC20(_from, _to, _amount, source); + emit SuperchainBurnt(_from, _amount); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol new file mode 100644 index 000000000000..9d13de80f4ca --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000028 +/// @title SuperchainERC20Bridge +/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the +/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain +/// binding. +contract SuperchainERC20Bridge is ISuperchainERC20Bridge { + /// @notice Address of the L2ToL2CrossDomainMessenger Predeploy. + address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1"; + + /// @notice Sends tokens to some target address on another chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external { + if (_to == address(0)) revert ZeroAddress(); + + ISuperchainERC20(_token).__superchainBurn(msg.sender, _amount); + + bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); + IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + + emit SendERC20(_token, msg.sender, _to, _amount, _chainId); + } + + /// @notice Relays tokens received from another chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. + function relayERC20(address _token, address _from, address _to, uint256 _amount) external { + if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + + if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { + revert InvalidCrossDomainSender(); + } + + uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + + ISuperchainERC20(_token).__superchainMint(_to, _amount); + + emit RelayERC20(_token, _from, _to, _amount, source); + } +} diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index 4788b70b1bc9..de6a51d7407c 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -5,24 +5,23 @@ pragma solidity 0.8.15; import { WETH98 } from "src/universal/WETH98.sol"; // Libraries -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; -import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; /// @title SuperchainWETH /// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains /// within the superchain. SuperchainWETH can be converted into native ETH on chains that /// do not use a custom gas token. -contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { +contract SuperchainWETH is WETH98, ISuperchainWETH, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.5 - string public constant version = "1.0.0-beta.5"; + /// @custom:semver 1.0.0-beta.7 + string public constant version = "1.0.0-beta.7"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -36,7 +35,7 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { super.withdraw(wad); } - /// @inheritdoc ISuperchainERC20Extensions + /// @inheritdoc ISuperchainWETH function sendERC20(address dst, uint256 wad, uint256 chainId) public { // Burn from user's balance. _burn(msg.sender, wad); @@ -57,12 +56,12 @@ contract SuperchainWETH is WETH98, ISuperchainERC20Extensions, ISemver { emit SendERC20(msg.sender, dst, wad, chainId); } - /// @inheritdoc ISuperchainERC20Extensions + /// @inheritdoc ISuperchainWETH function relayERC20(address from, address dst, uint256 wad) external { // Receive message from other chain. IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - if (msg.sender != address(messenger)) revert Unauthorized(); - if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized(); + if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger(); + if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender(); // Mint from ETHLiquidity contract. if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol index ed4ec1cef519..af2a35c8c87c 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL2StandardBridgeInterop.sol @@ -4,11 +4,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; - -interface IMintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; interface IL2StandardBridgeInterop is IStandardBridge { error InvalidDecimals(); diff --git a/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol new file mode 100644 index 000000000000..166fa0c8077f --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/IMintableAndBurnableERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IMintableAndBurnableERC20 +/// @notice Interface for mintable and burnable ERC20 tokens. +interface IMintableAndBurnableERC20 is IERC20 { + /// @notice Mints `_amount` of tokens to `_to`. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint(address _to, uint256 _amount) external; + + /// @notice Burns `_amount` of tokens from `_from`. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn(address _from, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol index 5f537c1f51ec..a887ecf0e030 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20.sol @@ -2,14 +2,22 @@ pragma solidity ^0.8.0; // Interfaces +import { ISuperchainERC20, ISuperchainERC20Extension } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + +/// @title IOptimismSuperchainERC20Errors +/// @notice Interface containing the errors added in the OptimismSuperchainERC20 implementation. +interface IOptimismSuperchainERC20Errors { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the L2StandardBridge + error OnlyL2StandardBridge(); +} /// @title IOptimismSuperchainERC20Extension /// @notice This interface is available on the OptimismSuperchainERC20 contract. -/// We declare it as a separate interface so that it can be used in -/// custom implementations of SuperchainERC20. -interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors { +interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extension, IOptimismSuperchainERC20Errors { /// @notice Emitted whenever tokens are minted for an account. /// @param account Address of the account tokens are being minted for. /// @param amount Amount of tokens minted. @@ -20,12 +28,12 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISupe /// @param amount Amount of tokens burned. event Burn(address indexed account, uint256 amount); - /// @notice Allows the L2StandardBridge to mint tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. function mint(address _to, uint256 _amount) external; - /// @notice Allows the L2StandardBridge to burn tokens. + /// @notice Allows the L2StandardBridge and SuperchainERC20Bridge to burn tokens. /// @param _from Address to burn tokens from. /// @param _amount Amount of tokens to burn. function burn(address _from, uint256 _amount) external; @@ -35,5 +43,5 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISupe } /// @title IOptimismSuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface. +/// @notice Combines Solady's ERC20 interface with the IOptimismSuperchainERC20Extension interface. interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol new file mode 100644 index 000000000000..aa23405fa527 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/IOptimismSuperchainERC20Factory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOptimismERC20Factory } from "./IOptimismERC20Factory.sol"; + +/// @title IOptimismSuperchainERC20Factory +/// @notice Interface for OptimismSuperchainERC20Factory. +interface IOptimismSuperchainERC20Factory is IOptimismERC20Factory { + /// @notice Deploys a OptimismSuperchainERC20 Beacon Proxy using CREATE3. + /// @param _remoteToken Address of the remote token. + /// @param _name Name of the OptimismSuperchainERC20. + /// @param _symbol Symbol of the OptimismSuperchainERC20. + /// @param _decimals Decimals of the OptimismSuperchainERC20. + /// @return _superchainERC20 Address of the OptimismSuperchainERC20 deployment. + function deploy( + address _remoteToken, + string memory _name, + string memory _symbol, + uint8 _decimals + ) + external + returns (address _superchainERC20); +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index 6ed17a9f46ec..47341c559719 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -4,55 +4,39 @@ pragma solidity ^0.8.0; // Interfaces import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol"; -/// @title ISuperchainERC20Extensions -/// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20. -/// Exists in case developers are already importing the ERC20 interface separately and -/// importing the full SuperchainERC20 interface would cause conflicting imports. -interface ISuperchainERC20Extensions { - /// @notice Emitted when tokens are sent from one chain to another. - /// @param from Address of the sender. - /// @param to Address of the recipient. - /// @param amount Number of tokens sent. - /// @param destination Chain ID of the destination chain. - event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); - - /// @notice Emitted whenever tokens are successfully relayed on this chain. - /// @param from Address of the msg.sender of sendERC20 on the source chain. - /// @param to Address of the recipient. - /// @param amount Amount of tokens relayed. - /// @param source Chain ID of the source chain. - event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); - - /// @notice Sends tokens to some target address on another chain. - /// @param _to Address to send tokens to. - /// @param _amount Amount of tokens to send. - /// @param _chainId Chain ID of the destination chain. - function sendERC20(address _to, uint256 _amount, uint256 _chainId) external; - - /// @notice Relays tokens received from another chain. - /// @param _from Address of the msg.sender of sendERC20 on the source chain. - /// @param _to Address to relay tokens to. - /// @param _amount Amount of tokens to relay. - function relayERC20(address _from, address _to, uint256 _amount) external; -} - /// @title ISuperchainERC20Errors /// @notice Interface containing the errors added in the SuperchainERC20 implementation. interface ISuperchainERC20Errors { - /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not - /// L2ToL2CrossDomainMessenger. - error CallerNotL2ToL2CrossDomainMessenger(); - - /// @notice Thrown when attempting to relay a message and the cross domain message sender is not this - /// SuperchainERC20. - error InvalidCrossDomainSender(); + /// @notice Thrown when attempting to mint or burn tokens and the function caller is not the SuperchainERC20Bridge. + error OnlySuperchainERC20Bridge(); +} - /// @notice Thrown when attempting to perform an operation and the account is the zero address. - error ZeroAddress(); +/// @title ISuperchainERC20Extension +/// @notice This interface is available on the SuperchainERC20 contract. +interface ISuperchainERC20Extension is ISuperchainERC20Errors { + /// @notice Emitted whenever tokens are minted for by the SuperchainERC20Bridge. + /// @param account Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event SuperchainMinted(address indexed account, uint256 amount); + + /// @notice Emitted whenever tokens are burned by the SuperchainERC20Bridge. + /// @param account Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event SuperchainBurnt(address indexed account, uint256 amount); + + /// @notice Allows the SuperchainERC20Bridge to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function __superchainMint(address _to, uint256 _amount) external; + + /// @notice Allows the SuperchainERC20Bridge to burn tokens. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function __superchainBurn(address _from, uint256 _amount) external; } /// @title ISuperchainERC20 -/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface. -interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors { +/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extension interface. +interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extension { function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol new file mode 100644 index 000000000000..8a3da87633bb --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "src/universal/interfaces/ISemver.sol"; + +/// @title ISuperchainERC20Bridge +/// @notice Interface for the SuperchainERC20Bridge contract. +interface ISuperchainERC20Bridge is ISemver { + /// @notice Thrown when attempting to perform an operation and the account is the zero address. + error ZeroAddress(); + + /// @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 the + /// SuperchainERC20Bridge. + 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 Sends tokens to some target address on another chain. + /// @param _token Token to send. + /// @param _to Address to send tokens to. + /// @param _amount Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId) external; + + /// @notice Relays tokens received from another chain. + /// @param _token Token to relay. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _to Address to relay tokens to. + /// @param _amount Amount of tokens to relay. + function relayERC20(address _token, address _from, address _to, uint256 _amount) external; +} diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol index 1204c328fc89..bccab456f5fd 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol @@ -1,35 +1,44 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { IWETH } from "src/universal/interfaces/IWETH.sol"; + interface ISuperchainWETH { + /// @notice Thrown when attempting a deposit or withdrawal and the chain uses a custom gas token. error NotCustomGasToken(); - error Unauthorized(); - event Approval(address indexed src, address indexed guy, uint256 wad); - event Deposit(address indexed dst, uint256 wad); + /// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not + /// L2ToL2CrossDomainMessenger. + error CallerNotL2ToL2CrossDomainMessenger(); + + /// @notice Thrown when attempting to relay a message and the cross domain message sender is not `address(this)` + error InvalidCrossDomainSender(); + + /// @notice Emitted whenever tokens are successfully relayed on this chain. + /// @param from Address of the msg.sender of sendERC20 on the source chain. + /// @param to Address of the recipient. + /// @param amount Amount of tokens relayed. + /// @param source Chain ID of the source chain. event RelayERC20(address indexed from, address indexed to, uint256 amount, uint256 source); + + /// @notice Emitted when tokens are sent from one chain to another. + /// @param from Address of the sender. + /// @param to Address of the recipient. + /// @param amount Number of tokens sent. + /// @param destination Chain ID of the destination chain. event SendERC20(address indexed from, address indexed to, uint256 amount, uint256 destination); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Withdrawal(address indexed src, uint256 wad); - - fallback() external payable; - - receive() external payable; - - function allowance(address, address) external view returns (uint256); - function approve(address guy, uint256 wad) external returns (bool); - function balanceOf(address) external view returns (uint256); - function decimals() external view returns (uint8); - function deposit() external payable; - function name() external view returns (string memory); - function relayERC20(address from, address dst, uint256 wad) external; - function sendERC20(address dst, uint256 wad, uint256 chainId) external; - function symbol() external view returns (string memory); - function totalSupply() external view returns (uint256); - function transfer(address dst, uint256 wad) external returns (bool); - function transferFrom(address src, address dst, uint256 wad) external returns (bool); - function version() external view returns (string memory); - function withdraw(uint256 wad) external; - - function __constructor__() external; + + /// @notice Sends tokens to some target address on another chain. + /// @param _dst Address to send tokens to. + /// @param _wad Amount of tokens to send. + /// @param _chainId Chain ID of the destination chain. + function sendERC20(address _dst, uint256 _wad, uint256 _chainId) external; + + /// @notice Relays tokens received from another chain. + /// @param _from Address of the msg.sender of sendERC20 on the source chain. + /// @param _dst Address to relay tokens to. + /// @param _wad Amount of tokens to relay. + function relayERC20(address _from, address _dst, uint256 _wad) external; } + +interface ISuperchainWETHERC20 is IWETH, ISuperchainWETH { } diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index c8fca4376bde..5bc1d75a1f06 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -105,6 +105,9 @@ library Predeploys { /// @notice Arbitrary address of the OptimismSuperchainERC20 implementation contract. address internal constant OPTIMISM_SUPERCHAIN_ERC20 = 0xB9415c6cA93bdC545D4c5177512FCC22EFa38F28; + /// @notice Address of the SuperchainERC20Bridge predeploy. + address internal constant SUPERCHAIN_ERC20_BRIDGE = 0x4200000000000000000000000000000000000028; + /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); @@ -135,6 +138,7 @@ library Predeploys { if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; if (_addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) return "OptimismSuperchainERC20Beacon"; + if (_addr == SUPERCHAIN_ERC20_BRIDGE) return "SuperchainERC20Bridge"; revert("Predeploys: unnamed predeploy"); } @@ -154,7 +158,8 @@ library Predeploys { || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) - || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON); + || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_BEACON) + || (_useInterop && _addr == SUPERCHAIN_ERC20_BRIDGE); } function isPredeployNamespace(address _addr) internal pure returns (bool) { diff --git a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol index ee993fe1110c..68fc374178ee 100644 --- a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol @@ -150,8 +150,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 23 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 17); + // 24 proxies have the implementation set if useInterop is true and 17 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 24 : 17); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( diff --git a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol index db8ca94e37c8..96f4ad4ba61f 100644 --- a/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol +++ b/packages/contracts-bedrock/test/L2/L2StandardBridgeInterop.t.sol @@ -9,7 +9,8 @@ import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { IL2StandardBridgeInterop, IMintableAndBurnable } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { IL2StandardBridgeInterop } from "src/L2/interfaces/IL2StandardBridgeInterop.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; @@ -57,9 +58,9 @@ contract L2StandardBridgeInterop_Test is Bridge_Initializer { } } -/// @notice Test suite when converting from a legacy token to a SuperchainERC20 token +/// @notice Test suite when converting from a legacy token to a OptimismSuperchainERC20 token contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a legacy token to a SuperchainERC20 token + /// @notice Set up the test for converting from a legacy token to a OptimismSuperchainERC20 token function _setUpLegacyToSuper(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -202,9 +203,11 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() + ); + _mockAndExpect( + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); @@ -212,9 +215,9 @@ contract L2StandardBridgeInterop_LegacyToSuper_Test is L2StandardBridgeInterop_T } } -/// @notice Test suite when converting from a SuperchainERC20 token to a legacy token +/// @notice Test suite when converting from a OptimismSuperchainERC20 token to a legacy token contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_Test { - /// @notice Set up the test for converting from a SuperchainERC20 token to a legacy token + /// @notice Set up the test for converting from a OptimismSuperchainERC20 token to a legacy token function _setUpSuperToLegacy(address _from, address _to) internal { // Assume _assumeAddress(_from); @@ -358,9 +361,11 @@ contract L2StandardBridgeInterop_SuperToLegacy_Test is L2StandardBridgeInterop_T // Mock and expect the `burn` and `mint` functions _mockAndExpect( - _from, abi.encodeWithSelector(IMintableAndBurnable.burn.selector, _caller, _amount), abi.encode() + _from, abi.encodeWithSelector(IMintableAndBurnableERC20.burn.selector, _caller, _amount), abi.encode() + ); + _mockAndExpect( + _to, abi.encodeWithSelector(IMintableAndBurnableERC20.mint.selector, _caller, _amount), abi.encode() ); - _mockAndExpect(_to, abi.encodeWithSelector(IMintableAndBurnable.mint.selector, _caller, _amount), abi.encode()); // Act vm.prank(_caller); diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol index b8de1468421b..239c785b51c0 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol @@ -8,7 +8,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; -import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; @@ -16,12 +15,11 @@ import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract +import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; import { - OptimismSuperchainERC20, IOptimismSuperchainERC20Extension, OnlyBridge -} from "src/L2/OptimismSuperchainERC20.sol"; - -// SuperchainERC20 Interfaces -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; + IOptimismSuperchainERC20Extension, + IOptimismSuperchainERC20Errors +} from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; /// @title OptimismSuperchainERC20Test /// @notice Contract for testing the OptimismSuperchainERC20 contract. @@ -29,22 +27,22 @@ contract OptimismSuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); address internal constant REMOTE_TOKEN = address(0x123); string internal constant NAME = "OptimismSuperchainERC20"; - string internal constant SYMBOL = "SCE"; + string internal constant SYMBOL = "OSC"; uint8 internal constant DECIMALS = 18; - address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE; + address internal constant L2_BRIDGE = Predeploys.L2_STANDARD_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20 public superchainERC20; + OptimismSuperchainERC20 public optimismSuperchainERC20Impl; + OptimismSuperchainERC20 public optimismSuperchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); + optimismSuperchainERC20Impl = new OptimismSuperchainERC20(); // Deploy the OptimismSuperchainERC20Beacon contract _deployBeacon(); - superchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); + optimismSuperchainERC20 = _deploySuperchainERC20Proxy(REMOTE_TOKEN, NAME, SYMBOL, DECIMALS); } /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract @@ -62,7 +60,9 @@ contract OptimismSuperchainERC20Test is Test { // Mock implementation address vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) + _impl, + abi.encodeWithSelector(IBeacon.implementation.selector), + abi.encode(address(optimismSuperchainERC20Impl)) ); } @@ -94,10 +94,10 @@ contract OptimismSuperchainERC20Test is Test { /// @notice Test that the contract's `initializer` sets the correct values. function test_initializer_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - assertEq(superchainERC20.remoteToken(), REMOTE_TOKEN); + assertEq(optimismSuperchainERC20.name(), NAME); + assertEq(optimismSuperchainERC20.symbol(), SYMBOL); + assertEq(optimismSuperchainERC20.decimals(), DECIMALS); + assertEq(optimismSuperchainERC20.remoteToken(), REMOTE_TOKEN); } /// @notice Tests the `initialize` function reverts when the contract is already initialized. @@ -113,30 +113,30 @@ contract OptimismSuperchainERC20Test is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); // Call the `initialize` function again - superchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); + optimismSuperchainERC20.initialize(_remoteToken, _name, _symbol, _decimals); } /// @notice Tests the `mint` function reverts when the caller is not the bridge. function testFuzz_mint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `mint` function with the non-bridge caller vm.prank(_caller); - superchainERC20.mint(_to, _amount); + optimismSuperchainERC20.mint(_to, _amount); } /// @notice Tests the `mint` function reverts when the amount is zero. function testFuzz_mint_zeroAddressTo_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `mint` function with the zero address - vm.prank(BRIDGE); - superchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint({ _to: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `mint` succeeds and emits the `Mint` event. @@ -145,47 +145,47 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_to != ZERO_ADDRESS); // Get the total supply and balance of `_to` before the mint to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _toBalanceBefore = optimismSuperchainERC20.balanceOf(_to); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); // Look for the emit of the `Mint` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IOptimismSuperchainERC20Extension.Mint(_to, _amount); // Call the `mint` function with the bridge caller - vm.prank(BRIDGE); - superchainERC20.mint(_to, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_to, _amount); // Check the total supply and balance of `_to` after the mint were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(optimismSuperchainERC20.balanceOf(_to), _toBalanceBefore + _amount); } /// @notice Tests the `burn` function reverts when the caller is not the bridge. function testFuzz_burn_callerNotBridge_reverts(address _caller, address _from, uint256 _amount) public { // Ensure the caller is not the bridge - vm.assume(_caller != BRIDGE); + vm.assume(_caller != L2_BRIDGE); - // Expect the revert with `OnlyBridge` selector - vm.expectRevert(OnlyBridge.selector); + // Expect the revert with `OnlyL2StandardBridge` selector + vm.expectRevert(IOptimismSuperchainERC20Errors.OnlyL2StandardBridge.selector); // Call the `burn` function with the non-bridge caller vm.prank(_caller); - superchainERC20.burn(_from, _amount); + optimismSuperchainERC20.burn(_from, _amount); } /// @notice Tests the `burn` function reverts when the amount is zero. function testFuzz_burn_zeroAddressFrom_reverts(uint256 _amount) public { // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + vm.expectRevert(IOptimismSuperchainERC20Errors.ZeroAddress.selector); // Call the `burn` function with the zero address - vm.prank(BRIDGE); - superchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn({ _from: ZERO_ADDRESS, _amount: _amount }); } /// @notice Tests the `burn` burns the amount and emits the `Burn` event. @@ -194,161 +194,28 @@ contract OptimismSuperchainERC20Test is Test { vm.assume(_from != ZERO_ADDRESS); // Mint some tokens to `_from` so then they can be burned - vm.prank(BRIDGE); - superchainERC20.mint(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.mint(_from, _amount); // Get the total supply and balance of `_from` before the burn to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); + uint256 _totalSupplyBefore = optimismSuperchainERC20.totalSupply(); + uint256 _fromBalanceBefore = optimismSuperchainERC20.balanceOf(_from); // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); // Look for the emit of the `Burn` event - vm.expectEmit(address(superchainERC20)); + vm.expectEmit(address(optimismSuperchainERC20)); emit IOptimismSuperchainERC20Extension.Burn(_from, _amount); // Call the `burn` function with the bridge caller - vm.prank(BRIDGE); - superchainERC20.burn(_from, _amount); + vm.prank(L2_BRIDGE); + optimismSuperchainERC20.burn(_from, _amount); // Check the total supply and balance of `_from` after the burn were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); - } - - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); - - // Call the `sendERC20` function with the zero address - vm.prank(BRIDGE); - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _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) external { - // Ensure `_sender` 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(BRIDGE); - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions - uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); - - // Look for the emit of the `SendERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); - - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); - - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); - - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); - - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_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(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - 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 = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); - - // Look for the emit of the `Transfer` event - vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - - // Look for the emit of the `RelayERC20` event - vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); - - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); - - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + assertEq(optimismSuperchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(optimismSuperchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } /// @notice Tests the `decimals` function always returns the correct value. @@ -375,17 +242,17 @@ contract OptimismSuperchainERC20Test is Test { assertEq(_newSuperchainERC20.symbol(), _symbol); } - /// @notice Tests that the `supportsInterface` function returns true for the `IOptimismSuperchainERC20` interface. + /// @notice Tests that the `supportsInterface` function returns true for the `ISuperchainERC20` interface. function test_supportInterface_succeeds() public view { - assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId)); - assertTrue(superchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IERC165).interfaceId)); + assertTrue(optimismSuperchainERC20.supportsInterface(type(IOptimismSuperchainERC20Extension).interfaceId)); } /// @notice Tests that the `supportsInterface` function returns false for any other interface than the - /// `IOptimismSuperchainERC20` one. + /// `ISuperchainERC20` one. function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view { vm.assume(_interfaceId != type(IERC165).interfaceId); vm.assume(_interfaceId != type(IOptimismSuperchainERC20Extension).interfaceId); - assertFalse(superchainERC20.supportsInterface(_interfaceId)); + assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId)); } } diff --git a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol index 3951ebf7452e..3636317156a7 100644 --- a/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol +++ b/packages/contracts-bedrock/test/L2/OptimismSuperchainERC20Factory.t.sol @@ -1,53 +1,30 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity 0.8.15; // Testing utilities -import { Test } from "forge-std/Test.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { CREATE3, Bytes32AddressLib } from "@rari-capital/solmate/src/utils/CREATE3.sol"; -import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; // Target contract -import { OptimismSuperchainERC20Factory, OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20Factory.sol"; +import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; /// @title OptimismSuperchainERC20FactoryTest /// @notice Contract for testing the OptimismSuperchainERC20Factory contract. -contract OptimismSuperchainERC20FactoryTest is Test { +contract OptimismSuperchainERC20FactoryTest is Bridge_Initializer { using Bytes32AddressLib for bytes32; - OptimismSuperchainERC20 public superchainERC20Impl; - OptimismSuperchainERC20Factory public superchainERC20Factory; + event OptimismSuperchainERC20Created( + address indexed superchainToken, address indexed remoteToken, address deployer + ); /// @notice Sets up the test suite. - function setUp() public { - superchainERC20Impl = new OptimismSuperchainERC20(); - - // Deploy the OptimismSuperchainERC20Beacon contract - _deployBeacon(); - - superchainERC20Factory = new OptimismSuperchainERC20Factory(); - } - - /// @notice Deploy the OptimismSuperchainERC20Beacon predeploy contract - function _deployBeacon() internal { - // Deploy the OptimismSuperchainERC20Beacon implementation - address _addr = Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; - address _impl = Predeploys.predeployToCodeNamespace(_addr); - vm.etch(_impl, vm.getDeployedCode("OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon")); - - // Deploy the ERC1967Proxy contract at the Predeploy - bytes memory code = vm.getDeployedCode("universal/Proxy.sol:Proxy"); - vm.etch(_addr, code); - EIP1967Helper.setAdmin(_addr, Predeploys.PROXY_ADMIN); - EIP1967Helper.setImplementation(_addr, _impl); - - // Mock implementation address - vm.mockCall( - _impl, abi.encodeWithSelector(IBeacon.implementation.selector), abi.encode(address(superchainERC20Impl)) - ); + function setUp() public override { + super.enableInterop(); + super.setUp(); } /// @notice Test that calling `deploy` with valid parameters succeeds. @@ -62,22 +39,22 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals)); - address deployment = _calculateTokenAddress(salt, address(superchainERC20Factory)); + address deployment = _calculateTokenAddress(salt, address(l2OptimismSuperchainERC20Factory)); - vm.expectEmit(address(superchainERC20Factory)); - emit OptimismSuperchainERC20Factory.OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); + vm.expectEmit(address(l2OptimismSuperchainERC20Factory)); + emit OptimismSuperchainERC20Created(deployment, _remoteToken, _caller); // Act vm.prank(_caller); - address addr = superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + address addr = l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); // Assert assertTrue(addr == deployment); - assertTrue(OptimismSuperchainERC20(deployment).decimals() == _decimals); - assertTrue(OptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); - assertEq(OptimismSuperchainERC20(deployment).name(), _name); - assertEq(OptimismSuperchainERC20(deployment).symbol(), _symbol); - assertEq(superchainERC20Factory.deployments(deployment), _remoteToken); + assertTrue(IOptimismSuperchainERC20(deployment).decimals() == _decimals); + assertTrue(IOptimismSuperchainERC20(deployment).remoteToken() == _remoteToken); + assertEq(IOptimismSuperchainERC20(deployment).name(), _name); + assertEq(IOptimismSuperchainERC20(deployment).symbol(), _symbol); + assertEq(l2OptimismSuperchainERC20Factory.deployments(deployment), _remoteToken); } /// @notice Test that calling `deploy` with the same parameters twice reverts. @@ -92,13 +69,13 @@ contract OptimismSuperchainERC20FactoryTest is Test { { // Arrange vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); vm.expectRevert(bytes("DEPLOYMENT_FAILED")); // Act vm.prank(_caller); - superchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); + l2OptimismSuperchainERC20Factory.deploy(_remoteToken, _name, _symbol, _decimals); } /// @notice Precalculates the address of the token contract using CREATE3. diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index 66b20c1b911a..30b758a38e6d 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -3,60 +3,35 @@ pragma solidity 0.8.25; // Testing utilities import { Test } from "forge-std/Test.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol"; +import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol"; +import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol"; +import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol"; +import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol"; // Target contract -import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; -import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; - -/// @notice Mock contract for the SuperchainERC20 contract so tests can mint tokens. -contract SuperchainERC20Mock is SuperchainERC20 { - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor(string memory __name, string memory __symbol, uint8 __decimals) { - _name = __name; - _symbol = __symbol; - _decimals = __decimals; - } - - function mint(address _account, uint256 _amount) public { - _mint(_account, _amount); - } - - function name() public view virtual override returns (string memory) { - return _name; - } +import { SuperchainERC20, ISuperchainERC20Extension } from "src/L2/SuperchainERC20.sol"; +import { ISuperchainERC20Errors } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { SuperchainERC20Implementation_MockContract } from "test/mocks/SuperchainERC20Implementation.sol"; - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function decimals() public view virtual override returns (uint8) { - return _decimals; - } -} /// @title SuperchainERC20Test /// @notice Contract for testing the SuperchainERC20 contract. - contract SuperchainERC20Test is Test { address internal constant ZERO_ADDRESS = address(0); - string internal constant NAME = "SuperchainERC20"; - string internal constant SYMBOL = "SCE"; - uint8 internal constant DECIMALS = 18; + address internal constant SUPERCHAIN_ERC20_BRIDGE = Predeploys.SUPERCHAIN_ERC20_BRIDGE; address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; - SuperchainERC20 public superchainERC20Impl; - SuperchainERC20Mock public superchainERC20; + SuperchainERC20 public superchainERC20; /// @notice Sets up the test suite. function setUp() public { - superchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, DECIMALS); + superchainERC20 = new SuperchainERC20Implementation_MockContract(); } /// @notice Helper function to setup a mock and expect a call to it. @@ -65,159 +40,91 @@ contract SuperchainERC20Test is Test { vm.expectCall(_receiver, _calldata); } - /// @notice Test that the contract's `constructor` sets the correct values. - function test_constructor_succeeds() public view { - assertEq(superchainERC20.name(), NAME); - assertEq(superchainERC20.symbol(), SYMBOL); - assertEq(superchainERC20.decimals(), DECIMALS); - } + /// @notice Tests the `mint` function reverts when the caller is not the bridge. + function testFuzz___superchainMint_callerNotBridge_reverts(address _caller, address _to, uint256 _amount) public { + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - /// @notice Tests the `sendERC20` function reverts when the `_to` address is the zero address. - function testFuzz_sendERC20_zeroAddressTo_reverts(uint256 _amount, uint256 _chainId) public { - // Expect the revert with `ZeroAddress` selector - vm.expectRevert(ISuperchainERC20Errors.ZeroAddress.selector); + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); - // Call the `sendERC20` function with the zero address - superchainERC20.sendERC20({ _to: ZERO_ADDRESS, _amount: _amount, _chainId: _chainId }); + // Call the `mint` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.__superchainMint(_to, _amount); } - /// @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) external { - // Ensure `_sender` is not the zero address - vm.assume(_sender != ZERO_ADDRESS); + /// @notice Tests the `mint` succeeds and emits the `Mint` event. + function testFuzz___superchainMint_succeeds(address _to, uint256 _amount) public { + // Ensure `_to` is not the zero address vm.assume(_to != ZERO_ADDRESS); - // Mint some tokens to the sender so then they can be sent - superchainERC20.mint(_sender, _amount); - - // Get the total supply and balance of `_sender` before the send to compare later on the assertions + // Get the total supply and balance of `_to` before the mint to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender); + uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount); + emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); - // Look for the emit of the `SendERC20` event + // Look for the emit of the `SuperchainMinted` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId); - - // Mock the call over the `sendMessage` function and expect it to be called properly - bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount)); - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector( - IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20), _message - ), - abi.encode("") - ); - - // Call the `sendERC20` function - vm.prank(_sender); - superchainERC20.sendERC20(_to, _amount, _chainId); - - // Check the total supply and balance of `_sender` after the send were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); - assertEq(superchainERC20.balanceOf(_sender), _senderBalanceBefore - _amount); - } - - /// @notice Tests the `relayERC20` function reverts when the caller is not the L2ToL2CrossDomainMessenger. - function testFuzz_relayERC20_notMessenger_reverts(address _caller, address _to, uint256 _amount) public { - // Ensure the caller is not the messenger - vm.assume(_caller != MESSENGER); - vm.assume(_to != ZERO_ADDRESS); + emit ISuperchainERC20Extension.SuperchainMinted(_to, _amount); - // Expect the revert with `CallerNotL2ToL2CrossDomainMessenger` selector - vm.expectRevert(ISuperchainERC20Errors.CallerNotL2ToL2CrossDomainMessenger.selector); + // Call the `mint` function with the bridge caller + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint(_to, _amount); - // Call the `relayERC20` function with the non-messenger caller - vm.prank(_caller); - superchainERC20.relayERC20(_caller, _to, _amount); + // Check the total supply and balance of `_to` after the mint were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); } - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not - /// the same SuperchainERC20 address. - function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _crossDomainMessageSender, - address _to, + /// @notice Tests the `burn` function reverts when the caller is not the bridge. + function testFuzz___superchainBurn_callerNotBridge_reverts( + address _caller, + address _from, uint256 _amount ) public { - vm.assume(_to != ZERO_ADDRESS); - vm.assume(_crossDomainMessageSender != address(superchainERC20)); - - // Mock the call over the `crossDomainMessageSender` function setting a wrong sender - vm.mockCall( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(_crossDomainMessageSender) - ); + // Ensure the caller is not the bridge + vm.assume(_caller != SUPERCHAIN_ERC20_BRIDGE); - // Expect the revert with `InvalidCrossDomainSender` selector - vm.expectRevert(ISuperchainERC20Errors.InvalidCrossDomainSender.selector); + // Expect the revert with `OnlySuperchainERC20Bridge` selector + vm.expectRevert(ISuperchainERC20Errors.OnlySuperchainERC20Bridge.selector); - // Call the `relayERC20` function with the sender caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount); + // Call the `burn` function with the non-bridge caller + vm.prank(_caller); + superchainERC20.__superchainBurn(_from, _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 { + /// @notice Tests the `burn` burns the amount and emits the `SuperchainBurnt` event. + function testFuzz___superchainBurn_succeeds(address _from, uint256 _amount) public { + // Ensure `_from` is not the zero address vm.assume(_from != ZERO_ADDRESS); - vm.assume(_to != ZERO_ADDRESS); - // Mock the call over the `crossDomainMessageSender` function setting the same address as value - _mockAndExpect( - MESSENGER, - abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSender.selector), - abi.encode(address(superchainERC20)) - ); - - // Mock the call over the `crossDomainMessageSource` function setting the source chain ID as value - _mockAndExpect( - 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 + // Mint some tokens to `_from` so then they can be burned + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint(_from, _amount); + + // Get the total supply and balance of `_from` before the burn to compare later on the assertions uint256 _totalSupplyBefore = superchainERC20.totalSupply(); - uint256 _toBalanceBefore = superchainERC20.balanceOf(_to); + uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from); // Look for the emit of the `Transfer` event vm.expectEmit(address(superchainERC20)); - emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount); + emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount); - // Look for the emit of the `RelayERC20` event + // Look for the emit of the `SuperchainBurnt` event vm.expectEmit(address(superchainERC20)); - emit ISuperchainERC20Extensions.RelayERC20(_from, _to, _amount, _source); + emit ISuperchainERC20Extension.SuperchainBurnt(_from, _amount); - // Call the `relayERC20` function with the messenger caller - vm.prank(MESSENGER); - superchainERC20.relayERC20(_from, _to, _amount); + // Call the `burn` function with the bridge caller + vm.prank(SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainBurn(_from, _amount); - // Check the total supply and balance of `_to` after the relay were updated correctly - assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); - assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); - } - - /// @notice Tests the `name` function always returns the correct value. - function testFuzz_name_succeeds(string memory _name) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(_name, SYMBOL, DECIMALS); - assertEq(_newSuperchainERC20.name(), _name); - } - - /// @notice Tests the `symbol` function always returns the correct value. - function testFuzz_symbol_succeeds(string memory _symbol) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, _symbol, DECIMALS); - assertEq(_newSuperchainERC20.symbol(), _symbol); - } - - /// @notice Tests the `decimals` function always returns the correct value. - function testFuzz_decimals_succeeds(uint8 _decimals) public { - SuperchainERC20 _newSuperchainERC20 = new SuperchainERC20Mock(NAME, SYMBOL, _decimals); - assertEq(_newSuperchainERC20.decimals(), _decimals); + // Check the total supply and balance of `_from` after the burn were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount); } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol new file mode 100644 index 000000000000..7ec72e508dad --- /dev/null +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20Bridge.t.sol @@ -0,0 +1,189 @@ +// 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 { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; + +/// @title SuperchainERC20BridgeTest +/// @notice Contract for testing the SuperchainERC20Bridge contract. +contract SuperchainERC20BridgeTest 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(ISuperchainERC20Bridge.ZeroAddress.selector); + + // Call the `sendERC20` function with the zero address as `_to` + vm.prank(_sender); + superchainERC20Bridge.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) external { + // Ensure `_sender` is not the zero address + vm.assume(_sender != ZERO_ADDRESS); + + // Mint some tokens to the sender so then they can be sent + vm.prank(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + superchainERC20.__superchainMint(_sender, _amount); + + // Get the total supply and balance of `_sender` before the send to compare later on the assertions + uint256 _totalSupplyBefore = superchainERC20.totalSupply(); + uint256 _senderBalanceBefore = 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(superchainERC20Bridge)); + 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(superchainERC20Bridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); + _mockAndExpect( + Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, + abi.encodeWithSelector( + IL2ToL2CrossDomainMessenger.sendMessage.selector, _chainId, address(superchainERC20Bridge), _message + ), + abi.encode("") + ); + + // Call the `sendERC20` function + vm.prank(_sender); + superchainERC20Bridge.sendERC20(address(superchainERC20), _to, _amount, _chainId); + + // Check the total supply and balance of `_sender` after the send were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount); + assertEq(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 `CallerNotL2ToL2CrossDomainMessenger` selector + vm.expectRevert(ISuperchainERC20Bridge.CallerNotL2ToL2CrossDomainMessenger.selector); + + // Call the `relayERC20` function with the non-messenger caller + vm.prank(_caller); + superchainERC20Bridge.relayERC20(_token, _caller, _to, _amount); + } + + /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not + /// the same SuperchainERC20Bridge. + function testFuzz_relayERC20_notCrossDomainSender_reverts( + address _token, + address _crossDomainMessageSender, + address _to, + uint256 _amount + ) + public + { + vm.assume(_crossDomainMessageSender != address(superchainERC20Bridge)); + + // 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(ISuperchainERC20Bridge.InvalidCrossDomainSender.selector); + + // Call the `relayERC20` function with the sender caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainERC20Bridge.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(superchainERC20Bridge)) + ); + + // 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 = superchainERC20.totalSupply(); + uint256 _toBalanceBefore = 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(superchainERC20Bridge)); + 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); + superchainERC20Bridge.relayERC20(address(superchainERC20), _from, _to, _amount); + + // Check the total supply and balance of `_to` after the relay were updated correctly + assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount); + assertEq(superchainERC20.balanceOf(_to), _toBalanceBefore + _amount); + } +} diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index 361e982d7324..c9c523201c6a 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -6,11 +6,12 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; +import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; // Interfaces import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; +import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; /// @title SuperchainWETH_Test /// @notice Contract for testing the SuperchainWETH contract. @@ -320,7 +321,7 @@ contract SuperchainWETH_Test is CommonTest { // Nothing to arrange. // Act - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(ISuperchainWETH.CallerNotL2ToL2CrossDomainMessenger.selector); vm.prank(alice); superchainWeth.relayERC20(_sender, bob, _amount); @@ -345,7 +346,7 @@ contract SuperchainWETH_Test is CommonTest { ); // Act - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector); vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); superchainWeth.relayERC20(_sender, bob, _amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol index d90e90a2f811..915fe0b44200 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/OptimismSuperchainERC20.t.sol @@ -9,7 +9,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; import { ProtocolGuided } from "./fuzz/Protocol.guided.t.sol"; import { ProtocolUnguided } from "./fuzz/Protocol.unguided.t.sol"; @@ -42,7 +42,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply + fundsInTransit); @@ -65,7 +65,7 @@ contract OptimismSuperchainERC20Properties is Test { for (uint256 validChainId = 0; validChainId < handler.MAX_CHAINS(); validChainId++) { address supertoken = MESSENGER.superTokenAddresses(validChainId, currentSalt); if (supertoken != address(0)) { - totalSupply += OptimismSuperchainERC20(supertoken).totalSupply(); + totalSupply += SuperchainERC20(supertoken).totalSupply(); } } assertEq(trackedSupply, totalSupply); @@ -73,7 +73,7 @@ contract OptimismSuperchainERC20Properties is Test { } /// @custom:invariant many other assertion mode invariants are also defined under - /// `test/invariants/OptimismSuperchainERC20/fuzz/` . + /// `test/invariants/SuperchainERC20/fuzz/` . /// /// since setting`fail_on_revert=false` also ignores StdAssertion failures, this invariant explicitly asks the /// handler for assertion test failures diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md index 18970855ca46..1c8c90bb0301 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/PROPERTIES.md @@ -1,5 +1,9 @@ # Supertoken advanced testing +## Note + +This campaign will need to be updated the redesign `OptimismSuperchainERC20` redesign. Please delete this comment once the update is done. + ## Milestones The supertoken ecosystem consists of not just the supertoken contract, but the required changes to other contracts for liquidity to reach the former. @@ -12,8 +16,8 @@ Considering only the supertoken contract is merged into the `develop` branch, an ## Definitions -- *legacy token:* an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. -- *supertoken:* a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` +- _legacy token:_ an OptimismMintableERC20 or L2StandardERC20 token on the suprechain that has either been deployed by the factory after the liquidity migration upgrade to the latter, or has been deployed before it **but** added to factory’s `deployments` mapping as part of the upgrade. This testing campaign is not concerned with tokens on L1 or not listed in the factory’s `deployments` mapping. +- _supertoken:_ a SuperchainERC20 contract deployed by the `OptimismSuperchainERC20Factory` # Ecosystem properties @@ -28,7 +32,7 @@ legend: ## Unit test | id | milestone | description | tested | -| --- | --- | --- | --- | +| --- | ------------------- | ------------------------------------------------------------------------------------------ | ------ | | 0 | Factories | supertoken token address does not depend on the executing chain’s chainID | [ ] | | 1 | Factories | supertoken token address depends on remote token, name, symbol and decimals | [ ] | | 2 | Liquidity Migration | convert() should only allow converting legacy tokens to supertoken and viceversa | [ ] | @@ -40,18 +44,18 @@ legend: ## Valid state | id | milestone | description | tested | -| --- | --- | --- | --- | -| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [x] | -| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [~] | +| --- | --------- | ------------------------------------------------------------------------------ | ------ | +| 6 | SupERC20 | calls to sendERC20 succeed as long as caller has enough balance | [] | +| 7 | SupERC20 | calls to relayERC20 always succeed as long as the cross-domain caller is valid | [] | ## Variable transition | id | milestone | description | tested | -| --- | --- | --- | --- | -| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [x] | -| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [x] | -| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [x] | -| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [x] | +| --- | ------------------- | ------------------------------------------------------------------------------------------------- | ------ | +| 8 | SupERC20 | sendERC20 with a value of zero does not modify accounting | [] | +| 9 | SupERC20 | relayERC20 with a value of zero does not modify accounting | [] | +| 10 | SupERC20 | sendERC20 decreases the token's totalSupply in the source chain exactly by the input amount | [] | +| 26 | SupERC20 | sendERC20 decreases the sender's balance in the source chain exactly by the input amount | [] | | 27 | SupERC20 | relayERC20 increases sender's balance in the destination chain exactly by the input amount | [x] | | 11 | SupERC20 | relayERC20 increases the token's totalSupply in the destination chain exactly by the input amount | [ ] | | 12 | Liquidity Migration | supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge | [~] | @@ -63,9 +67,9 @@ legend: ## High level | id | milestone | description | tested | -| --- | --- | --- | --- | -| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | -| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | +| --- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 17 | Liquidity Migration | only calls to convert(legacy, super) can increase a supertoken’s total supply across chains | [ ] | +| 18 | Liquidity Migration | only calls to convert(super, legacy) can decrease a supertoken’s total supply across chains | [ ] | | 19 | Liquidity Migration | sum of supertoken total supply across all chains is always <= to convert(legacy, super)- convert(super, legacy) | [~] | | 20 | SupERC20 | tokens sendERC20-ed on a source chain to a destination chain can be relayERC20-ed on it as long as the source chain is in the dependency set of the destination chain | [ ] | | 21 | Liquidity Migration | sum of supertoken total supply across all chains is = to convert(legacy, super)- convert(super, legacy) when all cross-chain messages are processed | [~] | @@ -76,7 +80,7 @@ As another layer of defense, the following properties are defined which assume b It’s worth noting that these properties will not hold for a live system | id | milestone | description | tested | -| --- | --- | --- | --- | -| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [x] | -| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [x] | +| --- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 22 | SupERC20 | sendERC20 decreases sender balance in source chain and increases receiver balance in destination chain exactly by the input amount | [] | +| 23 | SupERC20 | sendERC20 decreases total supply in source chain and increases it in destination chain exactly by the input amount | [] | | 24 | Liquidity Migration | sum of supertoken total supply across all chains is always equal to convert(legacy, super)- convert(super, legacy) | [~] | diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol index 536a4ea7025a..236c41873b6c 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.guided.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import { MockL2ToL2CrossDomainMessenger } from "../helpers/MockL2ToL2CrossDomainMessenger.t.sol"; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { ProtocolHandler } from "../handlers/Protocol.t.sol"; import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; @@ -21,7 +21,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { validateTokenDeployParams(params) { chainId = bound(chainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 supertoken = _deploySupertoken( + SuperchainERC20 supertoken = _deploySupertoken( remoteTokens[params.remoteTokenIndex], WORDS[params.nameIndex], WORDS[params.symbolIndex], @@ -32,100 +32,6 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(supertoken.totalSupply() == 0); } - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 22 - /// @custom:property sendERC20 decreases sender balance in source chain and increases receiver balance in - /// destination chain exactly by the input amount - /// @custom:property-id 23 - /// @custom:property sendERC20 decreases total supply in source chain and increases it in destination chain exactly - /// by the input amount - function fuzz_bridgeSupertokenAtomic( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - uint256 destinationBalanceBefore = destinationToken.balanceOf(recipient); - uint256 destinationSupplyBefore = destinationToken.totalSupply(); - - MESSENGER.setAtomic(true); - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - MESSENGER.setAtomic(false); - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - uint256 destinationBalanceAfter = destinationToken.balanceOf(recipient); - // no free mint - compatibleAssert( - sourceBalanceBefore + destinationBalanceBefore == sourceBalanceAfter + destinationBalanceAfter - ); - // 22 - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - compatibleAssert(destinationBalanceBefore + amount == destinationBalanceAfter); - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - uint256 destinationSupplyAfter = destinationToken.totalSupply(); - // 23 - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - compatibleAssert(destinationSupplyBefore + amount == destinationSupplyAfter); - } catch { - MESSENGER.setAtomic(false); - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId, - uint256 amount - ) - public - withActor(msg.sender) - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(currentActor()); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(currentActor()); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(currentActor()); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 11 /// @custom:property relayERC20 increases the token's totalSupply in the destination chain exactly by the input /// amount @@ -135,7 +41,7 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid function fuzz_relayERC20(uint256 messageIndex) external { MockL2ToL2CrossDomainMessenger.CrossChainMessage memory messageToRelay = MESSENGER.messageQueue(messageIndex); - OptimismSuperchainERC20 destinationToken = OptimismSuperchainERC20(messageToRelay.crossDomainMessageSender); + SuperchainERC20 destinationToken = SuperchainERC20(messageToRelay.crossDomainMessageSender); uint256 destinationSupplyBefore = destinationToken.totalSupply(); uint256 destinationBalanceBefore = destinationToken.balanceOf(messageToRelay.recipient); @@ -156,56 +62,4 @@ contract ProtocolGuided is ProtocolHandler, CompatibleAssert { compatibleAssert(false); } } - - /// @custom:property-id 8 - /// @custom:property calls to sendERC20 with a value of zero dont modify accounting - // @notice is a subset of fuzz_sendERC20, so we'll just call it - // instead of re-implementing it. Keeping the function for visibility of the property. - function fuzz_sendZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex, - uint256 destinationChainId - ) - external - { - fuzz_sendERC20(fromIndex, recipientIndex, destinationChainId, 0); - } - - /// @custom:property-id 9 - /// @custom:property calls to relayERC20 with a value of zero dont modify accounting - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice cant call fuzz_RelayERC20 internally since that pops a - /// random message, which we cannot guarantee has a value of zero - function fuzz_relayZeroDoesNotModifyAccounting( - uint256 fromIndex, - uint256 recipientIndex - ) - external - withActor(msg.sender) - { - fromIndex = bound(fromIndex, 0, allSuperTokens.length - 1); - address recipient = getActorByRawIndex(recipientIndex); - OptimismSuperchainERC20 token = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - uint256 balanceSenderBefore = token.balanceOf(currentActor()); - uint256 balanceRecipientBefore = token.balanceOf(recipient); - uint256 supplyBefore = token.totalSupply(); - - MESSENGER.setCrossDomainMessageSender(address(token)); - vm.prank(address(MESSENGER)); - try token.relayERC20(currentActor(), recipient, 0) { - MESSENGER.setCrossDomainMessageSender(address(0)); - } catch { - // should not revert because of 7, and if it *does* revert, I want the test suite - // to discard the sequence instead of potentially getting another - // error due to the crossDomainMessageSender being manually set - compatibleAssert(false); - } - uint256 balanceSenderAfter = token.balanceOf(currentActor()); - uint256 balanceRecipeintAfter = token.balanceOf(recipient); - uint256 supplyAfter = token.totalSupply(); - compatibleAssert(balanceSenderBefore == balanceSenderAfter); - compatibleAssert(balanceRecipientBefore == balanceRecipeintAfter); - compatibleAssert(supplyBefore == supplyAfter); - } } diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol index 90cad38baa99..db7c2243a1cd 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/fuzz/Protocol.unguided.t.sol @@ -10,78 +10,6 @@ import { CompatibleAssert } from "../helpers/CompatibleAssert.t.sol"; contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { using EnumerableMap for EnumerableMap.Bytes32ToUintMap; - /// @custom:property-id 7 - /// @custom:property calls to relayERC20 always succeed as long as the cross-domain caller is valid - /// @notice this ensures actors cant simply call relayERC20 and get tokens, no matter the system state - /// but there's still some possible work on how hard we can bork the system state with handlers calling - /// the L2ToL2CrossDomainMessenger or bridge directly (pending on non-atomic bridging) - function fuzz_relayERC20( - uint256 tokenIndex, - address sender, - address crossDomainMessageSender, - address recipient, - uint256 amount - ) - external - { - MESSENGER.setCrossDomainMessageSender(crossDomainMessageSender); - address token = allSuperTokens[bound(tokenIndex, 0, allSuperTokens.length)]; - vm.prank(sender); - try OptimismSuperchainERC20(token).relayERC20(sender, recipient, amount) { - MESSENGER.setCrossDomainMessageSender(address(0)); - compatibleAssert(sender == address(MESSENGER)); - compatibleAssert(crossDomainMessageSender == token); - // this increases the supply across chains without a call to - // `mint` by the MESSENGER, so it kind of breaks an invariant, but - // let's walk around that: - bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); - (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); - ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); - } catch { - compatibleAssert(sender != address(MESSENGER) || crossDomainMessageSender != token); - MESSENGER.setCrossDomainMessageSender(address(0)); - } - } - - /// @custom:property-id 6 - /// @custom:property calls to sendERC20 succeed as long as caller has enough balance - /// @custom:property-id 26 - /// @custom:property sendERC20 decreases sender balance in source chain exactly by the input amount - /// @custom:property-id 10 - /// @custom:property sendERC20 decreases total supply in source chain exactly by the input amount - function fuzz_sendERC20( - address sender, - address recipient, - uint256 fromIndex, - uint256 destinationChainId, - uint256 amount - ) - public - { - destinationChainId = bound(destinationChainId, 0, MAX_CHAINS - 1); - OptimismSuperchainERC20 sourceToken = OptimismSuperchainERC20(allSuperTokens[fromIndex]); - OptimismSuperchainERC20 destinationToken = - MESSENGER.crossChainMessageReceiver(address(sourceToken), destinationChainId); - bytes32 deploySalt = MESSENGER.superTokenInitDeploySalts(address(sourceToken)); - uint256 sourceBalanceBefore = sourceToken.balanceOf(sender); - uint256 sourceSupplyBefore = sourceToken.totalSupply(); - - vm.prank(sender); - try sourceToken.sendERC20(recipient, amount, destinationChainId) { - (, uint256 currentlyInTransit) = ghost_tokensInTransit.tryGet(deploySalt); - ghost_tokensInTransit.set(deploySalt, currentlyInTransit + amount); - // 26 - uint256 sourceBalanceAfter = sourceToken.balanceOf(sender); - compatibleAssert(sourceBalanceBefore - amount == sourceBalanceAfter); - // 10 - uint256 sourceSupplyAfter = sourceToken.totalSupply(); - compatibleAssert(sourceSupplyBefore - amount == sourceSupplyAfter); - } catch { - // 6 - compatibleAssert(address(destinationToken) == address(sourceToken) || sourceBalanceBefore < amount); - } - } - /// @custom:property-id 12 /// @custom:property supertoken total supply only increases on calls to mint() by the L2toL2StandardBridge function fuzz_mint(uint256 tokenIndex, address to, address sender, uint256 amount) external { @@ -89,7 +17,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); amount = bound(amount, 0, type(uint256).max - OptimismSuperchainERC20(token).totalSupply()); vm.prank(sender); - try OptimismSuperchainERC20(token).mint(to, amount) { + try OptimismSuperchainERC20(token).__superchainMint(to, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue + amount); @@ -105,7 +33,7 @@ contract ProtocolUnguided is ProtocolHandler, CompatibleAssert { bytes32 salt = MESSENGER.superTokenInitDeploySalts(token); uint256 senderBalance = OptimismSuperchainERC20(token).balanceOf(sender); vm.prank(sender); - try OptimismSuperchainERC20(token).burn(from, amount) { + try OptimismSuperchainERC20(token).__superchainBurn(from, amount) { compatibleAssert(sender == BRIDGE); (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(salt); ghost_totalSupplyAcrossChains.set(salt, currentValue - amount); diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol index 921495b467ab..9dbd5d5fc4a8 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/handlers/Protocol.t.sol @@ -79,7 +79,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { index = bound(index, 0, allSuperTokens.length - 1); address addr = allSuperTokens[index]; vm.prank(BRIDGE); - OptimismSuperchainERC20(addr).mint(currentActor(), amount); + OptimismSuperchainERC20(addr).__superchainMint(currentActor(), amount); // currentValue will be zero if key is not present (, uint256 currentValue) = ghost_totalSupplyAcrossChains.tryGet(MESSENGER.superTokenInitDeploySalts(addr)); ghost_totalSupplyAcrossChains.set(MESSENGER.superTokenInitDeploySalts(addr), currentValue + amount); @@ -164,7 +164,7 @@ contract ProtocolHandler is TestBase, StdUtils, Actors { bytes32 hackySalt = keccak256(abi.encode(remoteToken, name, symbol, decimals, chainId)); supertoken = OptimismSuperchainERC20( address( - // TODO: Use the SuperchainERC20 Beacon Proxy + // TODO: Use the OptimismSuperchainERC20 Beacon Proxy new ERC1967Proxy{ salt: hackySalt }( address(superchainERC20Impl), abi.encodeCall(OptimismSuperchainERC20.initialize, (remoteToken, name, symbol, decimals)) diff --git a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol index 6eb1c30e6799..b5d70596ed63 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismSuperchainERC20/helpers/MockL2ToL2CrossDomainMessenger.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol"; +import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; contract MockL2ToL2CrossDomainMessenger { @@ -41,9 +41,9 @@ contract MockL2ToL2CrossDomainMessenger { ) external view - returns (OptimismSuperchainERC20) + returns (SuperchainERC20) { - return OptimismSuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); + return SuperchainERC20(superTokenAddresses[destinationChainId][superTokenInitDeploySalts[sender]]); } function setCrossDomainMessageSender(address sender) external { diff --git a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol index 1e761b7ea162..e9b8a3be828a 100644 --- a/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/invariants/SuperchainWETH.t.sol @@ -10,7 +10,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; +import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol"; import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; /// @title SuperchainWETH_User @@ -29,7 +29,7 @@ contract SuperchainWETH_User is StdUtils { Vm internal vm; /// @notice The SuperchainWETH contract. - ISuperchainWETH internal weth; + ISuperchainWETHERC20 internal weth; /// @notice Mapping of sent messages. mapping(bytes32 => bool) internal sent; @@ -40,7 +40,7 @@ contract SuperchainWETH_User is StdUtils { /// @param _vm The Vm contract. /// @param _weth The SuperchainWETH contract. /// @param _balance The initial balance of the contract. - constructor(Vm _vm, ISuperchainWETH _weth, uint256 _balance) { + constructor(Vm _vm, ISuperchainWETHERC20 _weth, uint256 _balance) { vm = _vm; weth = _weth; vm.deal(address(this), _balance); diff --git a/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol new file mode 100644 index 000000000000..4f3dec45896c --- /dev/null +++ b/packages/contracts-bedrock/test/mocks/SuperchainERC20Implementation.sol @@ -0,0 +1,20 @@ +// 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 SuperchainERC20Implementation_MockContract is SuperchainERC20 { + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant override version = "1.0.0-beta.1"; + + function name() public pure override returns (string memory) { + return "SuperchainERC20"; + } + + function symbol() public pure override returns (string memory) { + return "SCE"; + } +} diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 3a09519203ac..2af8b6d04e40 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -38,17 +38,18 @@ import { IL2ToL1MessagePasser } from "src/L2/interfaces/IL2ToL1MessagePasser.sol import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IOptimismERC20Factory } from "src/L2/interfaces/IOptimismERC20Factory.sol"; +import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { IBaseFeeVault } from "src/L2/interfaces/IBaseFeeVault.sol"; import { ISequencerFeeVault } from "src/L2/interfaces/ISequencerFeeVault.sol"; import { IL1FeeVault } from "src/L2/interfaces/IL1FeeVault.sol"; import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; -import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; +import { ISuperchainWETHERC20 } from "src/L2/interfaces/ISuperchainWETH.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IWETH } from "src/universal/interfaces/IWETH.sol"; import { IGovernanceToken } from "src/governance/interfaces/IGovernanceToken.sol"; import { ILegacyMessagePasser } from "src/legacy/interfaces/ILegacyMessagePasser.sol"; +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; /// @title Setup /// @dev This contact is responsible for setting up the contracts in state. It currently @@ -104,12 +105,11 @@ contract Setup { IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN); ILegacyMessagePasser legacyMessagePasser = ILegacyMessagePasser(Predeploys.LEGACY_MESSAGE_PASSER); IWETH weth = IWETH(payable(Predeploys.WETH)); - ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); + ISuperchainWETHERC20 superchainWeth = ISuperchainWETHERC20(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); - - // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas - IOptimismERC20Factory l2OptimismSuperchainERC20Factory = - IOptimismERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); + IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = + IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. @@ -231,6 +231,7 @@ contract Setup { labelPredeploy(Predeploys.ETH_LIQUIDITY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); labelPredeploy(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + labelPredeploy(Predeploys.SUPERCHAIN_ERC20_BRIDGE); // L2 Preinstalls labelPreinstall(Preinstalls.MultiCall3);