diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index ecfb713d05a0..f42bdc83dcb7 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -6,12 +6,12 @@ GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() ( GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369242) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967382) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564362) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076577) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564356) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076571) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467019) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512701) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72618) -GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92970) +GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68312) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68943) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155607) \ No newline at end of file +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file 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 ceae376ee5c2..f93ab0e3b718 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/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index db35c5b37429..a7e52e505e1c 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -105,7 +105,7 @@ }, "src/L2/L2StandardBridgeInterop.sol": { "initCodeHash": "0x9bc28e8511a4593362c2517ff90b26f9c1ecee382cce3950b47ca08892a872ef", - "sourceCodeHash": "0x6c814f4536d9fb8f384ed2195957f868abd15252e36d6dd243f3d60349a61994" + "sourceCodeHash": "0x9caa8042e7639dfa63bab7ecd17d3bd5524340940965071ce3c7ff6a06fe7de5" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0x13fe3729beb9ed966c97bef09acb9fe5043fe651d453145073d05f2567fa988d", @@ -131,6 +131,10 @@ "initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74", "sourceCodeHash": "0xd56922cb04597dea469c65e5a49d4b3c50c171e603601e6f41da9517cae0b11a" }, + "src/L2/SuperchainERC20Bridge.sol": { + "initCodeHash": "0x802574bf35587e9a8dc2416e91b9fd1411c75d219545b8b55d25a75452459b10", + "sourceCodeHash": "0x109099936bacea72e4877e7745286b807f26d2e4f4e7eddfb949ff4eab861893" + }, "src/L2/SuperchainWETH.sol": { "initCodeHash": "0xd8766c7ab41d34d935febf5b48289f947804634bde38f8e346075b9f2d867275", "sourceCodeHash": "0x6c1691c0fb5c86f1fd67e23495725c2cd86567556602e8cc0f28104ad6114bf4" diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json new file mode 100644 index 000000000000..ecea3d7d420c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainERC20Bridge.json @@ -0,0 +1,155 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "relayERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "name": "sendERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "source", + "type": "uint256" + } + ], + "name": "RelayERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destination", + "type": "uint256" + } + ], + "name": "SendERC20", + "type": "event" + }, + { + "inputs": [], + "name": "CallerNotL2ToL2CrossDomainMessenger", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCrossDomainSender", + "type": "error" + } +] \ 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..5f5cfd0b4567 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(); @@ -24,14 +25,6 @@ error InvalidSuperchainERC20Address(); /// @notice Thrown when the remote addresses of the tokens are not the same. error InvalidTokenPair(); -/// TODO: Define a better naming convention for this interface. -/// @notice Interface for minting and burning tokens in the L2StandardBridge. -/// Used for StandardL2ERC20, OptimismMintableERC20 and OptimismSuperchainERC20. -interface MintableAndBurnable is IERC20 { - function mint(address, uint256) external; - function burn(address, uint256) external; -} - /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @title L2StandardBridgeInterop @@ -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); } diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol new file mode 100644 index 000000000000..07052b7331aa --- /dev/null +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20Bridge.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Libraries +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces +import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISuperchainERC20Bridge } from "src/L2/interfaces/ISuperchainERC20Bridge.sol"; +import { IMintableAndBurnableERC20 } from "src/L2/interfaces/IMintableAndBurnableERC20.sol"; +import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x4200000000000000000000000000000000000028 +/// @title SuperchainERC20Bridge +/// @notice The SuperchainERC20Bridge allows for the bridging of ERC20 tokens to make them fungible across the +/// Superchain. It builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain +/// binding. +contract SuperchainERC20Bridge is ISuperchainERC20Bridge, ISemver { + /// @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 override { + IMintableAndBurnableERC20(_token).burn(msg.sender, _amount); + + bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount)); + IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + + emit SendERC20(address(_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 override { + if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger(); + + if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) { + revert InvalidCrossDomainSender(); + } + + uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource(); + + IMintableAndBurnableERC20(_token).mint(_to, _amount); + + emit RelayERC20(address(_token), _from, _to, _amount, source); + } +} 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/ISuperchainERC20Bridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol new file mode 100644 index 000000000000..291e9077c8d6 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20Bridge.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title ISuperchainERC20Bridge +/// @notice Interface for the SuperchainERC20Bridge contract. +interface ISuperchainERC20Bridge { + /// @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/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/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2Genesis.t.sol index ee993fe1110c..68fc374178ee 100644 --- a/packages/contracts-bedrock/test/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/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/Predeploys.t.sol b/packages/contracts-bedrock/test/Predeploys.t.sol index 0d9e4879fd6b..89807a897bb2 100644 --- a/packages/contracts-bedrock/test/Predeploys.t.sol +++ b/packages/contracts-bedrock/test/Predeploys.t.sol @@ -16,7 +16,8 @@ contract PredeploysBaseTest is CommonTest { function _isInterop(address _addr) internal pure returns (bool) { return _addr == Predeploys.CROSS_L2_INBOX || _addr == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER || _addr == Predeploys.SUPERCHAIN_WETH || _addr == Predeploys.ETH_LIQUIDITY - || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON; + || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY || _addr == Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON + || _addr == Predeploys.SUPERCHAIN_ERC20_BRIDGE; } /// @dev Returns true if the address is a predeploy that has a different code in the interop mode. diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 3a09519203ac..a9be73e2a08e 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -49,6 +49,7 @@ 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 @@ -106,6 +107,7 @@ contract Setup { IWETH weth = IWETH(payable(Predeploys.WETH)); ISuperchainWETH superchainWeth = ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)); IETHLiquidity ethLiquidity = IETHLiquidity(Predeploys.ETH_LIQUIDITY); + ISuperchainERC20Bridge superchainERC20Bridge = ISuperchainERC20Bridge(Predeploys.SUPERCHAIN_ERC20_BRIDGE); // TODO: Replace with OptimismSuperchainERC20Factory when updating pragmas IOptimismERC20Factory l2OptimismSuperchainERC20Factory = @@ -231,6 +233,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);