From b956a56a30fa9dc3e1599d0389cf078feb1db6b2 Mon Sep 17 00:00:00 2001 From: Incede <33103370+Incede@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:47:21 +0200 Subject: [PATCH 01/15] Initial sync --- .../src/L1/L1StandardBridge.sol | 143 +++--------- .../src/L2/L2StandardBridge.sol | 56 +---- .../src/universal/StandardBridge.sol | 205 +----------------- 3 files changed, 43 insertions(+), 361 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 757c140c56e..a16a136c4ce 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; @@ -20,22 +19,6 @@ import { SystemConfig } from "src/L1/SystemConfig.sol"; /// of some token types that may not be properly supported by this contract include, but are /// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. contract L1StandardBridge is StandardBridge, ISemver { - /// @custom:legacy - /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. - /// @param from Address of the depositor. - /// @param to Address of the recipient on L2. - /// @param amount Amount of ETH deposited. - /// @param extraData Extra data attached to the deposit. - event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - - /// @custom:legacy - /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized. - /// @param from Address of the withdrawer. - /// @param to Address of the recipient on L1. - /// @param amount Amount of ETH withdrawn. - /// @param extraData Extra data attached to the withdrawal. - event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - /// @custom:legacy /// @notice Emitted whenever an ERC20 deposit is initiated. /// @param l1Token Address of the token on L1. @@ -74,6 +57,16 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @custom:semver 2.2.0 string public constant version = "2.2.0"; + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + + /// @notice The address of caller from Circle. + address public circleCaller; + /// @notice Address of the SuperchainConfig contract. SuperchainConfig public superchainConfig; @@ -81,21 +74,31 @@ contract L1StandardBridge is StandardBridge, ISemver { SystemConfig public systemConfig; /// @notice Constructs the L1StandardBridge contract. - constructor() StandardBridge() { + constructor( + address _l1USDC, + address _l2USDC, + address _otherBridge, + ) StandardBridge() { initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)), - _systemConfig: SystemConfig(address(0)) + _systemConfig: SystemConfig(address(0)), + _otherBridge }); + + l1USDC = _l1USDC; + l2USDC = _l2USDC; } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. + /// @param _otherBridge Contract for the other StandardBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, - SystemConfig _systemConfig + SystemConfig _systemConfig, + StandardBridge _otherBridge ) public initializer @@ -104,7 +107,7 @@ contract L1StandardBridge is StandardBridge, ISemver { systemConfig = _systemConfig; __StandardBridge_init({ _messenger: _messenger, - _otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)) + _otherBridge }); } @@ -113,39 +116,19 @@ contract L1StandardBridge is StandardBridge, ISemver { return superchainConfig.paused(); } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - receive() external payable override onlyEOA { - _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); - } - /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @custom:legacy - /// @notice Deposits some amount of ETH into the sender's account on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - /// Data supplied here will not be used to execute any code on L2 and is - /// only emitted as extra data for the convenience of off-chain tooling. - function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable onlyEOA { - _initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData); - } + /// @inheritdoc IUSDCBurnableSourceBridge + function burnAllLockedUSDC() external override { + require(msg.sender == guardian(), "SuperchainConfig: only guardian can burn all USDC"); + // @note Only bridged USDC will be burned. We may refund the rest if possible. + uint256 _balance = totalBridgedUSDC; + totalBridgedUSDC = 0; - /// @custom:legacy - /// @notice Deposits some amount of ETH into a target account on L2. - /// Note that if ETH is sent to a contract on L2 and the call fails, then that ETH will - /// be locked in the L2StandardBridge. ETH may be recoverable if the call can be - /// successfully replayed by increasing the amount of gas supplied to the call. If the - /// call will fail for any amount of gas, then the ETH will be locked permanently. - /// @param _to Address of the recipient on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - /// Data supplied here will not be used to execute any code on L2 and is - /// only emitted as extra data for the convenience of off-chain tooling. - function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable { - _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData); + IFiatToken(l1USDC).burn(_balance); } /// @custom:legacy @@ -166,7 +149,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyEOA + onlyUSDCtoken { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -191,28 +174,11 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual + onlyUSDCtoken(l1Token, l2Token) { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } - /// @custom:legacy - /// @notice Finalizes a withdrawal of ETH from L2. - /// @param _from Address of the withdrawer on L2. - /// @param _to Address of the recipient on L1. - /// @param _amount Amount of ETH to withdraw. - /// @param _extraData Optional data forwarded from L2. - function finalizeETHWithdrawal( - address _from, - address _to, - uint256 _amount, - bytes calldata _extraData - ) - external - payable - { - finalizeBridgeETH(_from, _to, _amount, _extraData); - } - /// @custom:legacy /// @notice Finalizes a withdrawal of ERC20 tokens from L2. /// @param _l1Token Address of the token on L1. @@ -232,6 +198,8 @@ contract L1StandardBridge is StandardBridge, ISemver { external { finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); + // update total supply + // warnning check there is no reentrancy } /// @custom:legacy @@ -241,15 +209,6 @@ contract L1StandardBridge is StandardBridge, ISemver { return address(otherBridge); } - /// @notice Internal function for initiating an ETH deposit. - /// @param _from Address of the sender on L1. - /// @param _to Address of the recipient on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal { - _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData); - } - /// @notice Internal function for initiating an ERC20 deposit. /// @param _l1Token Address of the L1 token being deposited. /// @param _l2Token Address of the corresponding token on L2. @@ -272,38 +231,6 @@ contract L1StandardBridge is StandardBridge, ISemver { _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); } - /// @inheritdoc StandardBridge - /// @notice Emits the legacy ETHDepositInitiated event followed by the ETHBridgeInitiated event. - /// This is necessary for backwards compatibility with the legacy bridge. - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit ETHDepositInitiated(_from, _to, _amount, _extraData); - super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); - } - - /// @inheritdoc StandardBridge - /// @notice Emits the legacy ERC20DepositInitiated event followed by the ERC20BridgeInitiated - /// event. This is necessary for backwards compatibility with the legacy bridge. - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData); - super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); - } - /// @inheritdoc StandardBridge /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized /// event. This is necessary for backwards compatibility with the legacy bridge. diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 1472d0fd9e8..64fd5afd39c 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { L1Block } from "src/L2/L1Block.sol"; @@ -69,13 +68,6 @@ contract L2StandardBridge is StandardBridge, ISemver { }); } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - receive() external payable override onlyEOA { - _initiateWithdrawal( - Predeploys.LEGACY_ERC20_ETH, msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("") - ); - } - /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken(); @@ -83,8 +75,6 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1. - /// This function only works with OptimismMintableERC20 tokens or ether. Use the - /// `bridgeERC20` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _amount Amount of the L2 token to withdraw. @@ -100,6 +90,7 @@ contract L2StandardBridge is StandardBridge, ISemver { payable virtual onlyEOA + onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); @@ -107,12 +98,6 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1 to a target account on L1. - /// Note that if ETH is sent to a contract on L1 and the call fails, then that ETH will - /// be locked in the L1StandardBridge. ETH may be recoverable if the call can be - /// successfully replayed by increasing the amount of gas supplied to the call. If the - /// call will fail for any amount of gas, then the ETH will be locked permanently. - /// This function only works with OptimismMintableERC20 tokens or ether. Use the - /// `bridgeERC20To` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _to Recipient account on L1. @@ -129,6 +114,7 @@ contract L2StandardBridge is StandardBridge, ISemver { external payable virtual + onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); @@ -159,44 +145,10 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - if (_l2Token == Predeploys.LEGACY_ERC20_ETH) { - _initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData); - } else { - address l1Token = OptimismMintableERC20(_l2Token).l1Token(); - _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - } - } - /// @notice Emits the legacy WithdrawalInitiated event followed by the ETHBridgeInitiated event. - /// This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); - super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); - } + address l1Token = (_l2Token).l1Token(); + _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - /// @notice Emits the legacy DepositFinalized event followed by the ETHBridgeFinalized event. - /// This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); - super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index 140aba531e6..f95b02c3776 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -2,13 +2,10 @@ pragma solidity 0.8.15; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -23,16 +20,6 @@ abstract contract StandardBridge is Initializable { /// @notice The L2 gas limit set when eth is depoisited using the receive() function. uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; - /// @custom:legacy - /// @custom:spacer messenger - /// @notice Spacer for backwards compatibility. - bytes30 private spacer_0_2_30; - - /// @custom:legacy - /// @custom:spacer l2TokenBridge - /// @notice Spacer for backwards compatibility. - address private spacer_1_0_20; - /// @notice Mapping that stores deposits for a given pair of local and remote tokens. mapping(address => mapping(address => uint256)) public deposits; @@ -49,20 +36,6 @@ abstract contract StandardBridge is Initializable { /// would be a multiple of 50. uint256[45] private __gap; - /// @notice Emitted when an ETH bridge is initiated to the other chain. - /// @param from Address of the sender. - /// @param to Address of the receiver. - /// @param amount Amount of ETH sent. - /// @param extraData Extra data sent with the transaction. - event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - - /// @notice Emitted when an ETH bridge is finalized on this chain. - /// @param from Address of the sender. - /// @param to Address of the receiver. - /// @param amount Amount of ETH sent. - /// @param extraData Extra data sent with the transaction. - event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - /// @notice Emitted when an ERC20 bridge is initiated to the other chain. /// @param localToken Address of the ERC20 on this chain. /// @param remoteToken Address of the ERC20 on the remote chain. @@ -126,10 +99,6 @@ abstract contract StandardBridge is Initializable { otherBridge = _otherBridge; } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - /// Must be implemented by contracts that inherit. - receive() external payable virtual; - /// @notice Returns the address of the custom gas token and the token's decimals. function gasPayingToken() internal view virtual returns (address, uint8); @@ -163,31 +132,6 @@ abstract contract StandardBridge is Initializable { return false; } - /// @notice Sends ETH to the sender's address on the other chain. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA { - _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData); - } - - /// @notice Sends ETH to a receiver's address on the other chain. Note that if ETH is sent to a - /// smart contract and the call fails, the ETH will be temporarily locked in the - /// StandardBridge on the other chain until the call is replayed. If the call cannot be - /// replayed with any amount of gas (call always reverts), then the ETH will be - /// permanently locked in the StandardBridge on the other chain. ETH will also - /// be locked if the receiver is the other bridge, because finalizeBridgeETH will revert - /// in that case. - /// @param _to Address of the receiver. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable { - _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData); - } - /// @notice Sends ERC20 tokens to the sender's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -233,38 +177,6 @@ abstract contract StandardBridge is Initializable { _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); } - /// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other - /// StandardBridge contract on the remote chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH being bridged. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function finalizeBridgeETH( - address _from, - address _to, - uint256 _amount, - bytes calldata _extraData - ) - public - payable - onlyOtherBridge - { - require(paused() == false, "StandardBridge: paused"); - require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); - require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); - require(_to != address(this), "StandardBridge: cannot send to self"); - require(_to != address(messenger), "StandardBridge: cannot send to messenger"); - - // Emit the correct events. By default this will be _amount, but child - // contracts may override this function in order to emit legacy events as well. - _emitETHBridgeFinalized(_from, _to, _amount, _extraData); - - bool success = SafeCall.call(_to, gasleft(), _amount, hex""); - require(success, "StandardBridge: ETH transfer failed"); - } - /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other /// StandardBridge contract on the remote chain. /// @param _localToken Address of the ERC20 on this chain. @@ -287,54 +199,14 @@ abstract contract StandardBridge is Initializable { onlyOtherBridge { require(paused() == false, "StandardBridge: paused"); - if (_isOptimismMintableERC20(_localToken)) { - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - - OptimismMintableERC20(_localToken).mint(_to, _amount); - } else { - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; - IERC20(_localToken).safeTransfer(_to, _amount); - } + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; + IERC20(_localToken).safeTransfer(_to, _amount); // Emit the correct events. By default this will be ERC20BridgeFinalized, but child // contracts may override this function in order to emit legacy events as well. _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } - /// @notice Initiates a bridge of ETH through the CrossDomainMessenger. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH being bridged. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function _initiateBridgeETH( - address _from, - address _to, - uint256 _amount, - uint32 _minGasLimit, - bytes memory _extraData - ) - internal - { - require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); - require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); - - // Emit the correct events. By default this will be _amount, but child - // contracts may override this function in order to emit legacy events as well. - _emitETHBridgeInitiated(_from, _to, _amount, _extraData); - - messenger.sendMessage{ value: _amount }({ - _target: address(otherBridge), - _message: abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData), - _minGasLimit: _minGasLimit - }); - } - /// @notice Sends ERC20 tokens to a receiver's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -356,18 +228,8 @@ abstract contract StandardBridge is Initializable { internal { require(msg.value == 0, "StandardBridge: cannot send value"); - - if (_isOptimismMintableERC20(_localToken)) { - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - - OptimismMintableERC20(_localToken).burn(_from, _amount); - } else { - IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; - } + IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; // Emit the correct events. By default this will be ERC20BridgeInitiated, but child // contracts may override this function in order to emit legacy events as well. @@ -391,65 +253,6 @@ abstract contract StandardBridge is Initializable { }); } - /// @notice Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough. - /// Just the way we like it. - /// @param _token Address of the token to check. - /// @return True if the token is an OptimismMintableERC20. - function _isOptimismMintableERC20(address _token) internal view returns (bool) { - return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) - || ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId); - } - - /// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. - /// Calls can be saved in the future by combining this logic with - /// `_isOptimismMintableERC20`. - /// @param _mintableToken OptimismMintableERC20 to check against. - /// @param _otherToken Pair token to check. - /// @return True if the other token is the correct pair token for the OptimismMintableERC20. - function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { - if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) { - return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token(); - } else { - return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken(); - } - } - - /// @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event - /// when an ETH bridge is finalized on this chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH sent. - /// @param _extraData Extra data sent with the transaction. - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - virtual - { - emit ETHBridgeInitiated(_from, _to, _amount, _extraData); - } - - /// @notice Emits the ETHBridgeFinalized and if necessary the appropriate legacy event when an - /// ETH bridge is finalized on this chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH sent. - /// @param _extraData Extra data sent with the transaction. - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - virtual - { - emit ETHBridgeFinalized(_from, _to, _amount, _extraData); - } - /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy /// event when an ERC20 bridge is initiated to the other chain. /// @param _localToken Address of the ERC20 on this chain. From 1a39acaa235557961049c46dc9fa7ac9172c33ff Mon Sep 17 00:00:00 2001 From: Incede <33103370+Incede@users.noreply.github.com> Date: Fri, 21 Jun 2024 02:11:11 +0200 Subject: [PATCH 02/15] Cleanup --- .../src/L1/L1StandardBridge.sol | 51 +++++++------------ .../src/L2/L2StandardBridge.sol | 16 +++--- .../src/universal/StandardBridge.sol | 37 +++++++++++++- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index a16a136c4ce..21c75f04fd9 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; @@ -57,16 +58,6 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @custom:semver 2.2.0 string public constant version = "2.2.0"; - /// @notice The address of L1 USDC address. - // solhint-disable-next-line var-name-mixedcase - address public immutable l1USDC; - - /// @notice The address of L2 USDC address. - address public immutable l2USDC; - - /// @notice The address of caller from Circle. - address public circleCaller; - /// @notice Address of the SuperchainConfig contract. SuperchainConfig public superchainConfig; @@ -74,31 +65,28 @@ contract L1StandardBridge is StandardBridge, ISemver { SystemConfig public systemConfig; /// @notice Constructs the L1StandardBridge contract. - constructor( - address _l1USDC, - address _l2USDC, - address _otherBridge, - ) StandardBridge() { + constructor(address _otherBridge, address _l1USDC, address _l2USDC) StandardBridge() { initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)), _systemConfig: SystemConfig(address(0)), - _otherBridge + _otherBridgeAddress: _otherBridge, + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); - - l1USDC = _l1USDC; - l2USDC = _l2USDC; } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. - /// @param _otherBridge Contract for the other StandardBridge contract. + /// @param _otherBridgeAddress Contract for the other StandardBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, SystemConfig _systemConfig, - StandardBridge _otherBridge + address _otherBridgeAddress, + address _l1USDC, + address _l2USDC ) public initializer @@ -107,7 +95,9 @@ contract L1StandardBridge is StandardBridge, ISemver { systemConfig = _systemConfig; __StandardBridge_init({ _messenger: _messenger, - _otherBridge + _otherBridge: StandardBridge(payable(_otherBridgeAddress)), + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); } @@ -121,14 +111,13 @@ contract L1StandardBridge is StandardBridge, ISemver { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @inheritdoc IUSDCBurnableSourceBridge - function burnAllLockedUSDC() external override { - require(msg.sender == guardian(), "SuperchainConfig: only guardian can burn all USDC"); - // @note Only bridged USDC will be burned. We may refund the rest if possible. - uint256 _balance = totalBridgedUSDC; - totalBridgedUSDC = 0; - - IFiatToken(l1USDC).burn(_balance); + /// @notice Burns all locked USDC if the pbridge is already paused + function burnAllLockedUSDC() external { + require(paused() == true, "Bridge should be paused before burning all locked USDC"); + require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); + // uint256 _balance = totalBridgedUSDC; + deposits[l1USDC][l2USDC] = 0; + // IERC20(l1USDC).burn(_balance); // check if this needs to be done } /// @custom:legacy @@ -149,7 +138,6 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyUSDCtoken { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -174,7 +162,6 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyUSDCtoken(l1Token, l2Token) { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 64fd5afd39c..2d326f81aef 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -55,16 +55,18 @@ contract L2StandardBridge is StandardBridge, ISemver { string public constant version = "1.10.0"; /// @notice Constructs the L2StandardBridge contract. - constructor() StandardBridge() { - initialize({ _otherBridge: StandardBridge(payable(address(0))) }); + constructor(address _l1USDC, address _l2USDC) StandardBridge() { + initialize({ _otherBridge: StandardBridge(payable(address(0))), _l1USDC: _l1USDC, _l2USDC: _l2USDC }); } /// @notice Initializer. /// @param _otherBridge Contract for the corresponding bridge on the other chain. - function initialize(StandardBridge _otherBridge) public initializer { + function initialize(StandardBridge _otherBridge, address _l1USDC, address _l2USDC) public initializer { __StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), - _otherBridge: _otherBridge + _otherBridge: _otherBridge, + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); } @@ -90,7 +92,6 @@ contract L2StandardBridge is StandardBridge, ISemver { payable virtual onlyEOA - onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); @@ -114,7 +115,6 @@ contract L2StandardBridge is StandardBridge, ISemver { external payable virtual - onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); @@ -145,10 +145,8 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - - address l1Token = (_l2Token).l1Token(); + address l1Token = l1USDC; _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index f95b02c3776..e7e93ce8af9 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -23,6 +23,13 @@ abstract contract StandardBridge is Initializable { /// @notice Mapping that stores deposits for a given pair of local and remote tokens. mapping(address => mapping(address => uint256)) public deposits; + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + /// @notice Messenger contract on this domain. /// @custom:network-specific CrossDomainMessenger public messenger; @@ -90,13 +97,17 @@ abstract contract StandardBridge is Initializable { /// @param _otherBridge Contract for the other StandardBridge contract. function __StandardBridge_init( CrossDomainMessenger _messenger, - StandardBridge _otherBridge + StandardBridge _otherBridge, + address _l1USDC, + address _l2USDC ) internal onlyInitializing { messenger = _messenger; otherBridge = _otherBridge; + l1USDC = _l1USDC; + l2USDC = _l2USDC; } /// @notice Returns the address of the custom gas token and the token's decimals. @@ -199,6 +210,10 @@ abstract contract StandardBridge is Initializable { onlyOtherBridge { require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; IERC20(_localToken).safeTransfer(_to, _amount); @@ -228,6 +243,11 @@ abstract contract StandardBridge is Initializable { internal { require(msg.value == 0, "StandardBridge: cannot send value"); + require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; @@ -253,6 +273,21 @@ abstract contract StandardBridge is Initializable { }); } + /** + * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. + * Calls can be saved in the future by combining this logic with + * `_isOptimismMintableERC20`. + * + * @param _mintableToken OptimismMintableERC20 to check against. + * @param _otherToken Pair token to check. + * + * @return True if the other token is the correct pair token for the OptimismMintableERC20. + */ + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { + return + ((_mintableToken == l1USDC && _otherToken == l2USDC) || (_mintableToken == l2USDC && _otherToken == l1USDC)); + } + /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy /// event when an ERC20 bridge is initiated to the other chain. /// @param _localToken Address of the ERC20 on this chain. From 52c5d4d342babbc77d92fd47a7b99aabaf84222d Mon Sep 17 00:00:00 2001 From: Incede <33103370+Incede@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:47:21 +0200 Subject: [PATCH 03/15] Initial sync --- .../src/L1/L1StandardBridge.sol | 143 +++--------- .../src/L2/L2StandardBridge.sol | 56 +---- .../src/universal/StandardBridge.sol | 205 +----------------- 3 files changed, 43 insertions(+), 361 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 757c140c56e..a16a136c4ce 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; @@ -20,22 +19,6 @@ import { SystemConfig } from "src/L1/SystemConfig.sol"; /// of some token types that may not be properly supported by this contract include, but are /// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. contract L1StandardBridge is StandardBridge, ISemver { - /// @custom:legacy - /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. - /// @param from Address of the depositor. - /// @param to Address of the recipient on L2. - /// @param amount Amount of ETH deposited. - /// @param extraData Extra data attached to the deposit. - event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - - /// @custom:legacy - /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized. - /// @param from Address of the withdrawer. - /// @param to Address of the recipient on L1. - /// @param amount Amount of ETH withdrawn. - /// @param extraData Extra data attached to the withdrawal. - event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - /// @custom:legacy /// @notice Emitted whenever an ERC20 deposit is initiated. /// @param l1Token Address of the token on L1. @@ -74,6 +57,16 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @custom:semver 2.2.0 string public constant version = "2.2.0"; + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + + /// @notice The address of caller from Circle. + address public circleCaller; + /// @notice Address of the SuperchainConfig contract. SuperchainConfig public superchainConfig; @@ -81,21 +74,31 @@ contract L1StandardBridge is StandardBridge, ISemver { SystemConfig public systemConfig; /// @notice Constructs the L1StandardBridge contract. - constructor() StandardBridge() { + constructor( + address _l1USDC, + address _l2USDC, + address _otherBridge, + ) StandardBridge() { initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)), - _systemConfig: SystemConfig(address(0)) + _systemConfig: SystemConfig(address(0)), + _otherBridge }); + + l1USDC = _l1USDC; + l2USDC = _l2USDC; } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. + /// @param _otherBridge Contract for the other StandardBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, - SystemConfig _systemConfig + SystemConfig _systemConfig, + StandardBridge _otherBridge ) public initializer @@ -104,7 +107,7 @@ contract L1StandardBridge is StandardBridge, ISemver { systemConfig = _systemConfig; __StandardBridge_init({ _messenger: _messenger, - _otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)) + _otherBridge }); } @@ -113,39 +116,19 @@ contract L1StandardBridge is StandardBridge, ISemver { return superchainConfig.paused(); } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - receive() external payable override onlyEOA { - _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); - } - /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @custom:legacy - /// @notice Deposits some amount of ETH into the sender's account on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - /// Data supplied here will not be used to execute any code on L2 and is - /// only emitted as extra data for the convenience of off-chain tooling. - function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable onlyEOA { - _initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData); - } + /// @inheritdoc IUSDCBurnableSourceBridge + function burnAllLockedUSDC() external override { + require(msg.sender == guardian(), "SuperchainConfig: only guardian can burn all USDC"); + // @note Only bridged USDC will be burned. We may refund the rest if possible. + uint256 _balance = totalBridgedUSDC; + totalBridgedUSDC = 0; - /// @custom:legacy - /// @notice Deposits some amount of ETH into a target account on L2. - /// Note that if ETH is sent to a contract on L2 and the call fails, then that ETH will - /// be locked in the L2StandardBridge. ETH may be recoverable if the call can be - /// successfully replayed by increasing the amount of gas supplied to the call. If the - /// call will fail for any amount of gas, then the ETH will be locked permanently. - /// @param _to Address of the recipient on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - /// Data supplied here will not be used to execute any code on L2 and is - /// only emitted as extra data for the convenience of off-chain tooling. - function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable { - _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData); + IFiatToken(l1USDC).burn(_balance); } /// @custom:legacy @@ -166,7 +149,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyEOA + onlyUSDCtoken { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -191,28 +174,11 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual + onlyUSDCtoken(l1Token, l2Token) { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } - /// @custom:legacy - /// @notice Finalizes a withdrawal of ETH from L2. - /// @param _from Address of the withdrawer on L2. - /// @param _to Address of the recipient on L1. - /// @param _amount Amount of ETH to withdraw. - /// @param _extraData Optional data forwarded from L2. - function finalizeETHWithdrawal( - address _from, - address _to, - uint256 _amount, - bytes calldata _extraData - ) - external - payable - { - finalizeBridgeETH(_from, _to, _amount, _extraData); - } - /// @custom:legacy /// @notice Finalizes a withdrawal of ERC20 tokens from L2. /// @param _l1Token Address of the token on L1. @@ -232,6 +198,8 @@ contract L1StandardBridge is StandardBridge, ISemver { external { finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); + // update total supply + // warnning check there is no reentrancy } /// @custom:legacy @@ -241,15 +209,6 @@ contract L1StandardBridge is StandardBridge, ISemver { return address(otherBridge); } - /// @notice Internal function for initiating an ETH deposit. - /// @param _from Address of the sender on L1. - /// @param _to Address of the recipient on L2. - /// @param _minGasLimit Minimum gas limit for the deposit message on L2. - /// @param _extraData Optional data to forward to L2. - function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal { - _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData); - } - /// @notice Internal function for initiating an ERC20 deposit. /// @param _l1Token Address of the L1 token being deposited. /// @param _l2Token Address of the corresponding token on L2. @@ -272,38 +231,6 @@ contract L1StandardBridge is StandardBridge, ISemver { _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); } - /// @inheritdoc StandardBridge - /// @notice Emits the legacy ETHDepositInitiated event followed by the ETHBridgeInitiated event. - /// This is necessary for backwards compatibility with the legacy bridge. - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit ETHDepositInitiated(_from, _to, _amount, _extraData); - super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); - } - - /// @inheritdoc StandardBridge - /// @notice Emits the legacy ERC20DepositInitiated event followed by the ERC20BridgeInitiated - /// event. This is necessary for backwards compatibility with the legacy bridge. - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData); - super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); - } - /// @inheritdoc StandardBridge /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized /// event. This is necessary for backwards compatibility with the legacy bridge. diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 1472d0fd9e8..64fd5afd39c 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.15; import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { L1Block } from "src/L2/L1Block.sol"; @@ -69,13 +68,6 @@ contract L2StandardBridge is StandardBridge, ISemver { }); } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - receive() external payable override onlyEOA { - _initiateWithdrawal( - Predeploys.LEGACY_ERC20_ETH, msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("") - ); - } - /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken(); @@ -83,8 +75,6 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1. - /// This function only works with OptimismMintableERC20 tokens or ether. Use the - /// `bridgeERC20` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _amount Amount of the L2 token to withdraw. @@ -100,6 +90,7 @@ contract L2StandardBridge is StandardBridge, ISemver { payable virtual onlyEOA + onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); @@ -107,12 +98,6 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1 to a target account on L1. - /// Note that if ETH is sent to a contract on L1 and the call fails, then that ETH will - /// be locked in the L1StandardBridge. ETH may be recoverable if the call can be - /// successfully replayed by increasing the amount of gas supplied to the call. If the - /// call will fail for any amount of gas, then the ETH will be locked permanently. - /// This function only works with OptimismMintableERC20 tokens or ether. Use the - /// `bridgeERC20To` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _to Recipient account on L1. @@ -129,6 +114,7 @@ contract L2StandardBridge is StandardBridge, ISemver { external payable virtual + onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); @@ -159,44 +145,10 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - if (_l2Token == Predeploys.LEGACY_ERC20_ETH) { - _initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData); - } else { - address l1Token = OptimismMintableERC20(_l2Token).l1Token(); - _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - } - } - /// @notice Emits the legacy WithdrawalInitiated event followed by the ETHBridgeInitiated event. - /// This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); - super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); - } + address l1Token = (_l2Token).l1Token(); + _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - /// @notice Emits the legacy DepositFinalized event followed by the ETHBridgeFinalized event. - /// This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - override - { - emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); - super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index 140aba531e6..f95b02c3776 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -2,13 +2,10 @@ pragma solidity 0.8.15; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; -import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -23,16 +20,6 @@ abstract contract StandardBridge is Initializable { /// @notice The L2 gas limit set when eth is depoisited using the receive() function. uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; - /// @custom:legacy - /// @custom:spacer messenger - /// @notice Spacer for backwards compatibility. - bytes30 private spacer_0_2_30; - - /// @custom:legacy - /// @custom:spacer l2TokenBridge - /// @notice Spacer for backwards compatibility. - address private spacer_1_0_20; - /// @notice Mapping that stores deposits for a given pair of local and remote tokens. mapping(address => mapping(address => uint256)) public deposits; @@ -49,20 +36,6 @@ abstract contract StandardBridge is Initializable { /// would be a multiple of 50. uint256[45] private __gap; - /// @notice Emitted when an ETH bridge is initiated to the other chain. - /// @param from Address of the sender. - /// @param to Address of the receiver. - /// @param amount Amount of ETH sent. - /// @param extraData Extra data sent with the transaction. - event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - - /// @notice Emitted when an ETH bridge is finalized on this chain. - /// @param from Address of the sender. - /// @param to Address of the receiver. - /// @param amount Amount of ETH sent. - /// @param extraData Extra data sent with the transaction. - event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - /// @notice Emitted when an ERC20 bridge is initiated to the other chain. /// @param localToken Address of the ERC20 on this chain. /// @param remoteToken Address of the ERC20 on the remote chain. @@ -126,10 +99,6 @@ abstract contract StandardBridge is Initializable { otherBridge = _otherBridge; } - /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. - /// Must be implemented by contracts that inherit. - receive() external payable virtual; - /// @notice Returns the address of the custom gas token and the token's decimals. function gasPayingToken() internal view virtual returns (address, uint8); @@ -163,31 +132,6 @@ abstract contract StandardBridge is Initializable { return false; } - /// @notice Sends ETH to the sender's address on the other chain. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA { - _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData); - } - - /// @notice Sends ETH to a receiver's address on the other chain. Note that if ETH is sent to a - /// smart contract and the call fails, the ETH will be temporarily locked in the - /// StandardBridge on the other chain until the call is replayed. If the call cannot be - /// replayed with any amount of gas (call always reverts), then the ETH will be - /// permanently locked in the StandardBridge on the other chain. ETH will also - /// be locked if the receiver is the other bridge, because finalizeBridgeETH will revert - /// in that case. - /// @param _to Address of the receiver. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable { - _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData); - } - /// @notice Sends ERC20 tokens to the sender's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -233,38 +177,6 @@ abstract contract StandardBridge is Initializable { _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); } - /// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other - /// StandardBridge contract on the remote chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH being bridged. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function finalizeBridgeETH( - address _from, - address _to, - uint256 _amount, - bytes calldata _extraData - ) - public - payable - onlyOtherBridge - { - require(paused() == false, "StandardBridge: paused"); - require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); - require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); - require(_to != address(this), "StandardBridge: cannot send to self"); - require(_to != address(messenger), "StandardBridge: cannot send to messenger"); - - // Emit the correct events. By default this will be _amount, but child - // contracts may override this function in order to emit legacy events as well. - _emitETHBridgeFinalized(_from, _to, _amount, _extraData); - - bool success = SafeCall.call(_to, gasleft(), _amount, hex""); - require(success, "StandardBridge: ETH transfer failed"); - } - /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other /// StandardBridge contract on the remote chain. /// @param _localToken Address of the ERC20 on this chain. @@ -287,54 +199,14 @@ abstract contract StandardBridge is Initializable { onlyOtherBridge { require(paused() == false, "StandardBridge: paused"); - if (_isOptimismMintableERC20(_localToken)) { - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - - OptimismMintableERC20(_localToken).mint(_to, _amount); - } else { - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; - IERC20(_localToken).safeTransfer(_to, _amount); - } + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; + IERC20(_localToken).safeTransfer(_to, _amount); // Emit the correct events. By default this will be ERC20BridgeFinalized, but child // contracts may override this function in order to emit legacy events as well. _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } - /// @notice Initiates a bridge of ETH through the CrossDomainMessenger. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH being bridged. - /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. - /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will - /// not be triggered with this data, but it will be emitted and can be used - /// to identify the transaction. - function _initiateBridgeETH( - address _from, - address _to, - uint256 _amount, - uint32 _minGasLimit, - bytes memory _extraData - ) - internal - { - require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); - require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); - - // Emit the correct events. By default this will be _amount, but child - // contracts may override this function in order to emit legacy events as well. - _emitETHBridgeInitiated(_from, _to, _amount, _extraData); - - messenger.sendMessage{ value: _amount }({ - _target: address(otherBridge), - _message: abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData), - _minGasLimit: _minGasLimit - }); - } - /// @notice Sends ERC20 tokens to a receiver's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -356,18 +228,8 @@ abstract contract StandardBridge is Initializable { internal { require(msg.value == 0, "StandardBridge: cannot send value"); - - if (_isOptimismMintableERC20(_localToken)) { - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - - OptimismMintableERC20(_localToken).burn(_from, _amount); - } else { - IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; - } + IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; // Emit the correct events. By default this will be ERC20BridgeInitiated, but child // contracts may override this function in order to emit legacy events as well. @@ -391,65 +253,6 @@ abstract contract StandardBridge is Initializable { }); } - /// @notice Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough. - /// Just the way we like it. - /// @param _token Address of the token to check. - /// @return True if the token is an OptimismMintableERC20. - function _isOptimismMintableERC20(address _token) internal view returns (bool) { - return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) - || ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId); - } - - /// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. - /// Calls can be saved in the future by combining this logic with - /// `_isOptimismMintableERC20`. - /// @param _mintableToken OptimismMintableERC20 to check against. - /// @param _otherToken Pair token to check. - /// @return True if the other token is the correct pair token for the OptimismMintableERC20. - function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { - if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) { - return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token(); - } else { - return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken(); - } - } - - /// @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event - /// when an ETH bridge is finalized on this chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH sent. - /// @param _extraData Extra data sent with the transaction. - function _emitETHBridgeInitiated( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - virtual - { - emit ETHBridgeInitiated(_from, _to, _amount, _extraData); - } - - /// @notice Emits the ETHBridgeFinalized and if necessary the appropriate legacy event when an - /// ETH bridge is finalized on this chain. - /// @param _from Address of the sender. - /// @param _to Address of the receiver. - /// @param _amount Amount of ETH sent. - /// @param _extraData Extra data sent with the transaction. - function _emitETHBridgeFinalized( - address _from, - address _to, - uint256 _amount, - bytes memory _extraData - ) - internal - virtual - { - emit ETHBridgeFinalized(_from, _to, _amount, _extraData); - } - /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy /// event when an ERC20 bridge is initiated to the other chain. /// @param _localToken Address of the ERC20 on this chain. From 39145b26a7e7ec4c0dc5003e06c1983c5ba43b5a Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Fri, 21 Jun 2024 10:29:17 +0200 Subject: [PATCH 04/15] Cleanup --- .../src/L1/L1StandardBridge.sol | 51 +++++++------------ .../src/L2/L2StandardBridge.sol | 16 +++--- .../src/universal/StandardBridge.sol | 37 +++++++++++++- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index a16a136c4ce..21c75f04fd9 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; @@ -57,16 +58,6 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @custom:semver 2.2.0 string public constant version = "2.2.0"; - /// @notice The address of L1 USDC address. - // solhint-disable-next-line var-name-mixedcase - address public immutable l1USDC; - - /// @notice The address of L2 USDC address. - address public immutable l2USDC; - - /// @notice The address of caller from Circle. - address public circleCaller; - /// @notice Address of the SuperchainConfig contract. SuperchainConfig public superchainConfig; @@ -74,31 +65,28 @@ contract L1StandardBridge is StandardBridge, ISemver { SystemConfig public systemConfig; /// @notice Constructs the L1StandardBridge contract. - constructor( - address _l1USDC, - address _l2USDC, - address _otherBridge, - ) StandardBridge() { + constructor(address _otherBridge, address _l1USDC, address _l2USDC) StandardBridge() { initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)), _systemConfig: SystemConfig(address(0)), - _otherBridge + _otherBridgeAddress: _otherBridge, + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); - - l1USDC = _l1USDC; - l2USDC = _l2USDC; } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. - /// @param _otherBridge Contract for the other StandardBridge contract. + /// @param _otherBridgeAddress Contract for the other StandardBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, SystemConfig _systemConfig, - StandardBridge _otherBridge + address _otherBridgeAddress, + address _l1USDC, + address _l2USDC ) public initializer @@ -107,7 +95,9 @@ contract L1StandardBridge is StandardBridge, ISemver { systemConfig = _systemConfig; __StandardBridge_init({ _messenger: _messenger, - _otherBridge + _otherBridge: StandardBridge(payable(_otherBridgeAddress)), + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); } @@ -121,14 +111,13 @@ contract L1StandardBridge is StandardBridge, ISemver { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @inheritdoc IUSDCBurnableSourceBridge - function burnAllLockedUSDC() external override { - require(msg.sender == guardian(), "SuperchainConfig: only guardian can burn all USDC"); - // @note Only bridged USDC will be burned. We may refund the rest if possible. - uint256 _balance = totalBridgedUSDC; - totalBridgedUSDC = 0; - - IFiatToken(l1USDC).burn(_balance); + /// @notice Burns all locked USDC if the pbridge is already paused + function burnAllLockedUSDC() external { + require(paused() == true, "Bridge should be paused before burning all locked USDC"); + require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); + // uint256 _balance = totalBridgedUSDC; + deposits[l1USDC][l2USDC] = 0; + // IERC20(l1USDC).burn(_balance); // check if this needs to be done } /// @custom:legacy @@ -149,7 +138,6 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyUSDCtoken { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -174,7 +162,6 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual - onlyUSDCtoken(l1Token, l2Token) { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 64fd5afd39c..2d326f81aef 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -55,16 +55,18 @@ contract L2StandardBridge is StandardBridge, ISemver { string public constant version = "1.10.0"; /// @notice Constructs the L2StandardBridge contract. - constructor() StandardBridge() { - initialize({ _otherBridge: StandardBridge(payable(address(0))) }); + constructor(address _l1USDC, address _l2USDC) StandardBridge() { + initialize({ _otherBridge: StandardBridge(payable(address(0))), _l1USDC: _l1USDC, _l2USDC: _l2USDC }); } /// @notice Initializer. /// @param _otherBridge Contract for the corresponding bridge on the other chain. - function initialize(StandardBridge _otherBridge) public initializer { + function initialize(StandardBridge _otherBridge, address _l1USDC, address _l2USDC) public initializer { __StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), - _otherBridge: _otherBridge + _otherBridge: _otherBridge, + _l1USDC: _l1USDC, + _l2USDC: _l2USDC }); } @@ -90,7 +92,6 @@ contract L2StandardBridge is StandardBridge, ISemver { payable virtual onlyEOA - onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); @@ -114,7 +115,6 @@ contract L2StandardBridge is StandardBridge, ISemver { external payable virtual - onlyUSDCtoken { require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); @@ -145,10 +145,8 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - - address l1Token = (_l2Token).l1Token(); + address l1Token = l1USDC; _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); - } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index f95b02c3776..e7e93ce8af9 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -23,6 +23,13 @@ abstract contract StandardBridge is Initializable { /// @notice Mapping that stores deposits for a given pair of local and remote tokens. mapping(address => mapping(address => uint256)) public deposits; + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + /// @notice Messenger contract on this domain. /// @custom:network-specific CrossDomainMessenger public messenger; @@ -90,13 +97,17 @@ abstract contract StandardBridge is Initializable { /// @param _otherBridge Contract for the other StandardBridge contract. function __StandardBridge_init( CrossDomainMessenger _messenger, - StandardBridge _otherBridge + StandardBridge _otherBridge, + address _l1USDC, + address _l2USDC ) internal onlyInitializing { messenger = _messenger; otherBridge = _otherBridge; + l1USDC = _l1USDC; + l2USDC = _l2USDC; } /// @notice Returns the address of the custom gas token and the token's decimals. @@ -199,6 +210,10 @@ abstract contract StandardBridge is Initializable { onlyOtherBridge { require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; IERC20(_localToken).safeTransfer(_to, _amount); @@ -228,6 +243,11 @@ abstract contract StandardBridge is Initializable { internal { require(msg.value == 0, "StandardBridge: cannot send value"); + require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; @@ -253,6 +273,21 @@ abstract contract StandardBridge is Initializable { }); } + /** + * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. + * Calls can be saved in the future by combining this logic with + * `_isOptimismMintableERC20`. + * + * @param _mintableToken OptimismMintableERC20 to check against. + * @param _otherToken Pair token to check. + * + * @return True if the other token is the correct pair token for the OptimismMintableERC20. + */ + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { + return + ((_mintableToken == l1USDC && _otherToken == l2USDC) || (_mintableToken == l2USDC && _otherToken == l1USDC)); + } + /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy /// event when an ERC20 bridge is initiated to the other chain. /// @param _localToken Address of the ERC20 on this chain. From d622eb9f097d6a3711f977cdfb2e763d508ab22c Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 10:21:20 +0200 Subject: [PATCH 05/15] Revert changes to Standard bridge and add dedicated bridge files --- .../src/L1/L1DedicatedBridge.sol | 270 +++++++++++++++ .../src/L1/L1StandardBridge.sol | 134 ++++++-- .../src/L2/L2DedicatedBridge.sol | 199 +++++++++++ .../src/L2/L2StandardBridge.sol | 68 +++- .../src/universal/DedicatedBridge.sol | 320 ++++++++++++++++++ .../src/universal/StandardBridge.sol | 238 ++++++++++--- 6 files changed, 1158 insertions(+), 71 deletions(-) create mode 100644 packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol create mode 100644 packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol create mode 100644 packages/contracts-bedrock/src/universal/DedicatedBridge.sol diff --git a/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol new file mode 100644 index 00000000000..ba3aa9ffe73 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { StandardBridge } from "src/universal/StandardBridge.sol"; +import { ISemver } from "src/universal/ISemver.sol"; +import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; +import { SystemConfig } from "src/L1/SystemConfig.sol"; + +/// @custom:proxied +/// @title L1StandardBridge +/// @notice The L1StandardBridge is responsible for transfering ETH and ERC20 tokens between L1 and +/// L2. In the case that an ERC20 token is native to L1, it will be escrowed within this +/// contract. If the ERC20 token is native to L2, it will be burnt. Before Bedrock, ETH was +/// stored within this contract. After Bedrock, ETH is instead stored inside the +/// OptimismPortal contract. +/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples +/// of some token types that may not be properly supported by this contract include, but are +/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. +contract L1StandardBridge is StandardBridge, ISemver { + /// @custom:legacy + /// @notice Emitted whenever an ERC20 deposit is initiated. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the depositor. + /// @param to Address of the recipient on L2. + /// @param amount Amount of the ERC20 deposited. + /// @param extraData Extra data attached to the deposit. + event ERC20DepositInitiated( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @custom:legacy + /// @notice Emitted whenever an ERC20 withdrawal is finalized. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the withdrawer. + /// @param to Address of the recipient on L1. + /// @param amount Amount of the ERC20 withdrawn. + /// @param extraData Extra data attached to the withdrawal. + event ERC20WithdrawalFinalized( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @notice Semantic version. + /// @custom:semver 2.2.0 + string public constant version = "2.2.0"; + + /// @notice Address of the SuperchainConfig contract. + SuperchainConfig public superchainConfig; + + /// @notice Address of the SystemConfig contract. + SystemConfig public systemConfig; + + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + + /// @notice Constructs the L1StandardBridge contract. + constructor( + address _l1USDC, + address _l2USDC, + CrossDomainMessenger _messenger, + SuperchainConfig _superchainConfig, + SystemConfig _systemConfig, + address _otherBridgeAddress + ) + StandardBridge() + { + l1USDC = _l1USDC; + l2USDC = _l2USDC; + initialize({ + _messenger: _messenger, + _superchainConfig: _superchainConfig, + _systemConfig: _systemConfig, + _otherBridgeAddress: _otherBridgeAddress + }); + } + + /// @notice Initializer. + /// @param _messenger Contract for the CrossDomainMessenger on this network. + /// @param _superchainConfig Contract for the SuperchainConfig on this network. + /// @param _otherBridgeAddress Contract for the other StandardBridge contract. + function initialize( + CrossDomainMessenger _messenger, + SuperchainConfig _superchainConfig, + SystemConfig _systemConfig, + address _otherBridgeAddress + ) + public + initializer + { + superchainConfig = _superchainConfig; + systemConfig = _systemConfig; + __StandardBridge_init({ _messenger: _messenger, _otherBridge: StandardBridge(payable(_otherBridgeAddress)) }); + } + + /// @inheritdoc StandardBridge + function paused() public view override returns (bool) { + return superchainConfig.paused(); + } + + /// @inheritdoc StandardBridge + function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { + (addr_, decimals_) = systemConfig.gasPayingToken(); + } + + /// @notice Burns all locked USDC if the pbridge is already paused + function burnAllLockedUSDC() external { + require(paused() == true, "Bridge should be paused before burning all locked USDC"); + require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); + // uint256 _balance = totalBridgedUSDC; + deposits[l1USDC][l2USDC] = 0; + // IERC20(l1USDC).burn(_balance); // check if this needs to be done + } + + /// @custom:legacy + /// @notice Deposits some amount of ERC20 tokens into the sender's account on L2. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositERC20( + address _l1Token, + address _l2Token, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + virtual + { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Deposits some amount of ERC20 tokens into a target account on L2. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _to Address of the recipient on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositERC20To( + address _l1Token, + address _l2Token, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + virtual + { + _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Finalizes a withdrawal of ERC20 tokens from L2. + /// @param _l1Token Address of the token on L1. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _from Address of the withdrawer on L2. + /// @param _to Address of the recipient on L1. + /// @param _amount Amount of the ERC20 to withdraw. + /// @param _extraData Optional data forwarded from L2. + function finalizeERC20Withdrawal( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + external + { + finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); + // update total supply + // warnning check there is no reentrancy + } + + /// @custom:legacy + /// @notice Retrieves the access of the corresponding L2 bridge contract. + /// @return Address of the corresponding L2 bridge contract. + function l2TokenBridge() external view returns (address) { + return address(otherBridge); + } + + /// @notice Internal function for initiating an ERC20 deposit. + /// @param _l1Token Address of the L1 token being deposited. + /// @param _l2Token Address of the corresponding token on L2. + /// @param _from Address of the sender on L1. + /// @param _to Address of the recipient on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + function _initiateERC20Deposit( + address _l1Token, + address _l2Token, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); + } + + /// @inheritdoc StandardBridge + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view override returns (bool) { + return (_mintableToken == l1USDC && _otherToken == l2USDC); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitERC20BridgeInitiated( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitERC20BridgeFinalized( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 21c75f04fd9..3d1489f6b63 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; @@ -20,6 +20,22 @@ import { SystemConfig } from "src/L1/SystemConfig.sol"; /// of some token types that may not be properly supported by this contract include, but are /// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. contract L1StandardBridge is StandardBridge, ISemver { + /// @custom:legacy + /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated. + /// @param from Address of the depositor. + /// @param to Address of the recipient on L2. + /// @param amount Amount of ETH deposited. + /// @param extraData Extra data attached to the deposit. + event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @custom:legacy + /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized. + /// @param from Address of the withdrawer. + /// @param to Address of the recipient on L1. + /// @param amount Amount of ETH withdrawn. + /// @param extraData Extra data attached to the withdrawal. + event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); + /// @custom:legacy /// @notice Emitted whenever an ERC20 deposit is initiated. /// @param l1Token Address of the token on L1. @@ -65,28 +81,21 @@ contract L1StandardBridge is StandardBridge, ISemver { SystemConfig public systemConfig; /// @notice Constructs the L1StandardBridge contract. - constructor(address _otherBridge, address _l1USDC, address _l2USDC) StandardBridge() { + constructor() StandardBridge() { initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)), - _systemConfig: SystemConfig(address(0)), - _otherBridgeAddress: _otherBridge, - _l1USDC: _l1USDC, - _l2USDC: _l2USDC + _systemConfig: SystemConfig(address(0)) }); } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. - /// @param _otherBridgeAddress Contract for the other StandardBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, - SystemConfig _systemConfig, - address _otherBridgeAddress, - address _l1USDC, - address _l2USDC + SystemConfig _systemConfig ) public initializer @@ -95,9 +104,7 @@ contract L1StandardBridge is StandardBridge, ISemver { systemConfig = _systemConfig; __StandardBridge_init({ _messenger: _messenger, - _otherBridge: StandardBridge(payable(_otherBridgeAddress)), - _l1USDC: _l1USDC, - _l2USDC: _l2USDC + _otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)) }); } @@ -106,18 +113,39 @@ contract L1StandardBridge is StandardBridge, ISemver { return superchainConfig.paused(); } + /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. + receive() external payable override onlyEOA { + _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); + } + /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @notice Burns all locked USDC if the pbridge is already paused - function burnAllLockedUSDC() external { - require(paused() == true, "Bridge should be paused before burning all locked USDC"); - require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); - // uint256 _balance = totalBridgedUSDC; - deposits[l1USDC][l2USDC] = 0; - // IERC20(l1USDC).burn(_balance); // check if this needs to be done + /// @custom:legacy + /// @notice Deposits some amount of ETH into the sender's account on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable onlyEOA { + _initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Deposits some amount of ETH into a target account on L2. + /// Note that if ETH is sent to a contract on L2 and the call fails, then that ETH will + /// be locked in the L2StandardBridge. ETH may be recoverable if the call can be + /// successfully replayed by increasing the amount of gas supplied to the call. If the + /// call will fail for any amount of gas, then the ETH will be locked permanently. + /// @param _to Address of the recipient on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable { + _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData); } /// @custom:legacy @@ -138,6 +166,7 @@ contract L1StandardBridge is StandardBridge, ISemver { ) external virtual + onlyEOA { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -166,6 +195,24 @@ contract L1StandardBridge is StandardBridge, ISemver { _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } + /// @custom:legacy + /// @notice Finalizes a withdrawal of ETH from L2. + /// @param _from Address of the withdrawer on L2. + /// @param _to Address of the recipient on L1. + /// @param _amount Amount of ETH to withdraw. + /// @param _extraData Optional data forwarded from L2. + function finalizeETHWithdrawal( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + external + payable + { + finalizeBridgeETH(_from, _to, _amount, _extraData); + } + /// @custom:legacy /// @notice Finalizes a withdrawal of ERC20 tokens from L2. /// @param _l1Token Address of the token on L1. @@ -185,8 +232,6 @@ contract L1StandardBridge is StandardBridge, ISemver { external { finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData); - // update total supply - // warnning check there is no reentrancy } /// @custom:legacy @@ -196,6 +241,15 @@ contract L1StandardBridge is StandardBridge, ISemver { return address(otherBridge); } + /// @notice Internal function for initiating an ETH deposit. + /// @param _from Address of the sender on L1. + /// @param _to Address of the recipient on L2. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData) internal { + _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData); + } + /// @notice Internal function for initiating an ERC20 deposit. /// @param _l1Token Address of the L1 token being deposited. /// @param _l2Token Address of the corresponding token on L2. @@ -218,6 +272,38 @@ contract L1StandardBridge is StandardBridge, ISemver { _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); } + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ETHDepositInitiated event followed by the ETHBridgeInitiated event. + /// This is necessary for backwards compatibility with the legacy bridge. + function _emitETHBridgeInitiated( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ETHDepositInitiated(_from, _to, _amount, _extraData); + super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /// @inheritdoc StandardBridge + /// @notice Emits the legacy ERC20DepositInitiated event followed by the ERC20BridgeInitiated + /// event. This is necessary for backwards compatibility with the legacy bridge. + function _emitETHBridgeFinalized( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData); + super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); + } + /// @inheritdoc StandardBridge /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized /// event. This is necessary for backwards compatibility with the legacy bridge. @@ -253,4 +339,4 @@ contract L1StandardBridge is StandardBridge, ISemver { emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol new file mode 100644 index 00000000000..9b59ef62591 --- /dev/null +++ b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { StandardBridge } from "src/universal/StandardBridge.sol"; +import { ISemver } from "src/universal/ISemver.sol"; +import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; +import { L1Block } from "src/L2/L1Block.sol"; + +/// @custom:proxied +/// @custom:predeploy 0x4200000000000000000000000000000000000010 +/// @title L2StandardBridge +/// @notice The L2StandardBridge is responsible for transfering ETH and ERC20 tokens between L1 and +/// L2. In the case that an ERC20 token is native to L2, it will be escrowed within this +/// contract. If the ERC20 token is native to L1, it will be burnt. +/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples +/// of some token types that may not be properly supported by this contract include, but are +/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. +contract L2StandardBridge is StandardBridge, ISemver { + /// @custom:legacy + /// @notice Emitted whenever a withdrawal from L2 to L1 is initiated. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the withdrawer. + /// @param to Address of the recipient on L1. + /// @param amount Amount of the ERC20 withdrawn. + /// @param extraData Extra data attached to the withdrawal. + event WithdrawalInitiated( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @custom:legacy + /// @notice Emitted whenever an ERC20 deposit is finalized. + /// @param l1Token Address of the token on L1. + /// @param l2Token Address of the corresponding token on L2. + /// @param from Address of the depositor. + /// @param to Address of the recipient on L2. + /// @param amount Amount of the ERC20 deposited. + /// @param extraData Extra data attached to the deposit. + event DepositFinalized( + address indexed l1Token, + address indexed l2Token, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @custom:semver 1.10.0 + string public constant version = "1.10.0"; + + /// @notice The address of L1 USDC address. + // solhint-disable-next-line var-name-mixedcase + address public immutable l1USDC; + + /// @notice The address of L2 USDC address. + address public immutable l2USDC; + + /// @notice Constructs the L2StandardBridge contract. + constructor(address _l1USDC, address _l2USDC) StandardBridge() { + initialize({ _otherBridge: StandardBridge(payable(address(0))) }); + l1USDC = _l1USDC; + l2USDC = _l2USDC; + } + + /// @notice Initializer. + /// @param _otherBridge Contract for the corresponding bridge on the other chain. + function initialize(StandardBridge _otherBridge) public initializer { + __StandardBridge_init({ + _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), + _otherBridge: _otherBridge + }); + } + + /// @inheritdoc StandardBridge + function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { + (addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken(); + } + + /// @custom:legacy + /// @notice Initiates a withdrawal from L2 to L1. + /// Subject to be deprecated in the future. + /// @param _l2Token Address of the L2 token to withdraw. + /// @param _amount Amount of the L2 token to withdraw. + /// @param _minGasLimit Minimum gas limit to use for the transaction. + /// @param _extraData Extra data attached to the withdrawal. + function withdraw( + address _l2Token, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + payable + virtual + onlyEOA + { + require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); + _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Initiates a withdrawal from L2 to L1 to a target account on L1. + /// Subject to be deprecated in the future. + /// @param _l2Token Address of the L2 token to withdraw. + /// @param _to Recipient account on L1. + /// @param _amount Amount of the L2 token to withdraw. + /// @param _minGasLimit Minimum gas limit to use for the transaction. + /// @param _extraData Extra data attached to the withdrawal. + function withdrawTo( + address _l2Token, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + payable + virtual + { + require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); + _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + /// @custom:legacy + /// @notice Retrieves the access of the corresponding L1 bridge contract. + /// @return Address of the corresponding L1 bridge contract. + function l1TokenBridge() external view returns (address) { + return address(otherBridge); + } + + /// @custom:legacy + /// @notice Internal function to initiate a withdrawal from L2 to L1 to a target account on L1. + /// @param _l2Token Address of the L2 token to withdraw. + /// @param _from Address of the withdrawer. + /// @param _to Recipient account on L1. + /// @param _amount Amount of the L2 token to withdraw. + /// @param _minGasLimit Minimum gas limit to use for the transaction. + /// @param _extraData Extra data attached to the withdrawal. + function _initiateWithdrawal( + address _l2Token, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + address l1Token = l1USDC; + _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); + } + + /// @inheritdoc StandardBridge + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view override returns (bool) { + return (_mintableToken == l2USDC && _otherToken == l1USDC); + } + + /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated + /// event. This is necessary for backwards compatibility with the legacy bridge. + /// @inheritdoc StandardBridge + function _emitERC20BridgeInitiated( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit WithdrawalInitiated(_remoteToken, _localToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @notice Emits the legacy DepositFinalized event followed by the ERC20BridgeFinalized event. + /// This is necessary for backwards compatibility with the legacy bridge. + /// @inheritdoc StandardBridge + function _emitERC20BridgeFinalized( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit DepositFinalized(_remoteToken, _localToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 2d326f81aef..8946274f5b2 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.15; import { Predeploys } from "src/libraries/Predeploys.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { L1Block } from "src/L2/L1Block.sol"; @@ -55,21 +56,26 @@ contract L2StandardBridge is StandardBridge, ISemver { string public constant version = "1.10.0"; /// @notice Constructs the L2StandardBridge contract. - constructor(address _l1USDC, address _l2USDC) StandardBridge() { - initialize({ _otherBridge: StandardBridge(payable(address(0))), _l1USDC: _l1USDC, _l2USDC: _l2USDC }); + constructor() StandardBridge() { + initialize({ _otherBridge: StandardBridge(payable(address(0))) }); } /// @notice Initializer. /// @param _otherBridge Contract for the corresponding bridge on the other chain. - function initialize(StandardBridge _otherBridge, address _l1USDC, address _l2USDC) public initializer { + function initialize(StandardBridge _otherBridge) public initializer { __StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), - _otherBridge: _otherBridge, - _l1USDC: _l1USDC, - _l2USDC: _l2USDC + _otherBridge: _otherBridge }); } + /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. + receive() external payable override onlyEOA { + _initiateWithdrawal( + Predeploys.LEGACY_ERC20_ETH, msg.sender, msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, bytes("") + ); + } + /// @inheritdoc StandardBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken(); @@ -77,6 +83,8 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1. + /// This function only works with OptimismMintableERC20 tokens or ether. Use the + /// `bridgeERC20` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _amount Amount of the L2 token to withdraw. @@ -99,6 +107,12 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:legacy /// @notice Initiates a withdrawal from L2 to L1 to a target account on L1. + /// Note that if ETH is sent to a contract on L1 and the call fails, then that ETH will + /// be locked in the L1StandardBridge. ETH may be recoverable if the call can be + /// successfully replayed by increasing the amount of gas supplied to the call. If the + /// call will fail for any amount of gas, then the ETH will be locked permanently. + /// This function only works with OptimismMintableERC20 tokens or ether. Use the + /// `bridgeERC20To` function to bridge native L2 tokens to L1. /// Subject to be deprecated in the future. /// @param _l2Token Address of the L2 token to withdraw. /// @param _to Recipient account on L1. @@ -145,8 +159,44 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - address l1Token = l1USDC; - _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); + if (_l2Token == Predeploys.LEGACY_ERC20_ETH) { + _initiateBridgeETH(_from, _to, _amount, _minGasLimit, _extraData); + } else { + address l1Token = OptimismMintableERC20(_l2Token).l1Token(); + _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); + } + } + + /// @notice Emits the legacy WithdrawalInitiated event followed by the ETHBridgeInitiated event. + /// This is necessary for backwards compatibility with the legacy bridge. + /// @inheritdoc StandardBridge + function _emitETHBridgeInitiated( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); + super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /// @notice Emits the legacy DepositFinalized event followed by the ETHBridgeFinalized event. + /// This is necessary for backwards compatibility with the legacy bridge. + /// @inheritdoc StandardBridge + function _emitETHBridgeFinalized( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + override + { + emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, _from, _to, _amount, _extraData); + super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated @@ -184,4 +234,4 @@ contract L2StandardBridge is StandardBridge, ISemver { emit DepositFinalized(_remoteToken, _localToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} +} \ No newline at end of file diff --git a/packages/contracts-bedrock/src/universal/DedicatedBridge.sol b/packages/contracts-bedrock/src/universal/DedicatedBridge.sol new file mode 100644 index 00000000000..3b0c2854537 --- /dev/null +++ b/packages/contracts-bedrock/src/universal/DedicatedBridge.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeCall } from "src/libraries/SafeCall.sol"; +import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +/// @custom:upgradeable +/// @title StandardBridge +/// @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges. It handles +/// the core bridging logic, including escrowing tokens that are native to the local chain +/// and minting/burning tokens that are native to the remote chain. +abstract contract StandardBridge is Initializable { + using SafeERC20 for IERC20; + + /// @notice The L2 gas limit set when eth is depoisited using the receive() function. + uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; + + /// @notice Mapping that stores deposits for a given pair of local and remote tokens. + mapping(address => mapping(address => uint256)) public deposits; + + /// @notice Messenger contract on this domain. + /// @custom:network-specific + CrossDomainMessenger public messenger; + + /// @notice Corresponding bridge on the other domain. + /// @custom:network-specific + StandardBridge public otherBridge; + + /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + /// A gap size of 45 was chosen here, so that the first slot used in a child contract + /// would be a multiple of 50. + uint256[45] private __gap; + + /// @notice Emitted when an ERC20 bridge is initiated to the other chain. + /// @param localToken Address of the ERC20 on this chain. + /// @param remoteToken Address of the ERC20 on the remote chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of the ERC20 sent. + /// @param extraData Extra data sent with the transaction. + event ERC20BridgeInitiated( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @notice Emitted when an ERC20 bridge is finalized on this chain. + /// @param localToken Address of the ERC20 on this chain. + /// @param remoteToken Address of the ERC20 on the remote chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of the ERC20 sent. + /// @param extraData Extra data sent with the transaction. + event ERC20BridgeFinalized( + address indexed localToken, + address indexed remoteToken, + address indexed from, + address to, + uint256 amount, + bytes extraData + ); + + /// @notice Only allow EOAs to call the functions. Note that this is not safe against contracts + /// calling code within their constructors, but also doesn't really matter since we're + /// just trying to prevent users accidentally depositing with smart contract wallets. + modifier onlyEOA() { + require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA"); + _; + } + + /// @notice Ensures that the caller is a cross-chain message from the other bridge. + modifier onlyOtherBridge() { + require( + msg.sender == address(messenger) && messenger.xDomainMessageSender() == address(otherBridge), + "StandardBridge: function can only be called from the other bridge" + ); + _; + } + + /// @notice Initializer. + /// @param _messenger Contract for CrossDomainMessenger on this network. + /// @param _otherBridge Contract for the other StandardBridge contract. + function __StandardBridge_init( + CrossDomainMessenger _messenger, + StandardBridge _otherBridge + ) + internal + onlyInitializing + { + messenger = _messenger; + otherBridge = _otherBridge; + } + + /// @notice Returns the address of the custom gas token and the token's decimals. + function gasPayingToken() internal view virtual returns (address, uint8); + + /// @notice Returns whether the chain uses a custom gas token or not. + function isCustomGasToken() internal view returns (bool) { + (address token,) = gasPayingToken(); + return token != Constants.ETHER; + } + + /// @notice Getter for messenger contract. + /// Public getter is legacy and will be removed in the future. Use `messenger` instead. + /// @return Contract of the messenger on this domain. + /// @custom:legacy + function MESSENGER() external view returns (CrossDomainMessenger) { + return messenger; + } + + /// @notice Getter for the other bridge contract. + /// Public getter is legacy and will be removed in the future. Use `otherBridge` instead. + /// @return Contract of the bridge on the other network. + /// @custom:legacy + function OTHER_BRIDGE() external view returns (StandardBridge) { + return otherBridge; + } + + /// @notice This function should return true if the contract is paused. + /// On L1 this function will check the SuperchainConfig for its paused status. + /// On L2 this function should be a no-op. + /// @return Whether or not the contract is paused. + function paused() public view virtual returns (bool) { + return false; + } + + /// @notice Sends ERC20 tokens to the sender's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeERC20( + address _localToken, + address _remoteToken, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + public + virtual + onlyEOA + { + _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @notice Sends ERC20 tokens to a receiver's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _to Address of the receiver. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeERC20To( + address _localToken, + address _remoteToken, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + public + virtual + { + _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other + /// StandardBridge contract on the remote chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 being bridged. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function finalizeBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + public + onlyOtherBridge + { + require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; + IERC20(_localToken).safeTransfer(_to, _amount); + + // Emit the correct events. By default this will be ERC20BridgeFinalized, but child + // contracts may override this function in order to emit legacy events as well. + _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @notice Sends ERC20 tokens to a receiver's address on the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the corresponding token on the remote chain. + /// @param _to Address of the receiver. + /// @param _amount Amount of local tokens to deposit. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function _initiateBridgeERC20( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + require(msg.value == 0, "StandardBridge: cannot send value"); + require(paused() == false, "StandardBridge: paused"); + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; + + // Emit the correct events. By default this will be ERC20BridgeInitiated, but child + // contracts may override this function in order to emit legacy events as well. + _emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + + messenger.sendMessage({ + _target: address(otherBridge), + _message: abi.encodeWithSelector( + this.finalizeBridgeERC20.selector, + // Because this call will be executed on the remote chain, we reverse the order of + // the remote and local token addresses relative to their order in the + // finalizeBridgeERC20 function. + _remoteToken, + _localToken, + _from, + _to, + _amount, + _extraData + ), + _minGasLimit: _minGasLimit + }); + } + + /** + * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. + * Calls can be saved in the future by combining this logic with + * `_isOptimismMintableERC20`. + * + * @param _mintableToken OptimismMintableERC20 to check against. + * @param _otherToken Pair token to check. + * + * @return True if the other token is the correct pair token for the OptimismMintableERC20. + */ + function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view virtual returns (bool); + + /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy + /// event when an ERC20 bridge is initiated to the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the ERC20 on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 sent. + /// @param _extraData Extra data sent with the transaction. + function _emitERC20BridgeInitiated( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /// @notice Emits the ERC20BridgeFinalized event and if necessary the appropriate legacy + /// event when an ERC20 bridge is initiated to the other chain. + /// @param _localToken Address of the ERC20 on this chain. + /// @param _remoteToken Address of the ERC20 on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of the ERC20 sent. + /// @param _extraData Extra data sent with the transaction. + function _emitERC20BridgeFinalized( + address _localToken, + address _remoteToken, + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index e7e93ce8af9..bbcb9e8e492 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -2,10 +2,13 @@ pragma solidity 0.8.15; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; +import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -20,15 +23,18 @@ abstract contract StandardBridge is Initializable { /// @notice The L2 gas limit set when eth is depoisited using the receive() function. uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000; - /// @notice Mapping that stores deposits for a given pair of local and remote tokens. - mapping(address => mapping(address => uint256)) public deposits; + /// @custom:legacy + /// @custom:spacer messenger + /// @notice Spacer for backwards compatibility. + bytes30 private spacer_0_2_30; - /// @notice The address of L1 USDC address. - // solhint-disable-next-line var-name-mixedcase - address public immutable l1USDC; + /// @custom:legacy + /// @custom:spacer l2TokenBridge + /// @notice Spacer for backwards compatibility. + address private spacer_1_0_20; - /// @notice The address of L2 USDC address. - address public immutable l2USDC; + /// @notice Mapping that stores deposits for a given pair of local and remote tokens. + mapping(address => mapping(address => uint256)) public deposits; /// @notice Messenger contract on this domain. /// @custom:network-specific @@ -43,6 +49,20 @@ abstract contract StandardBridge is Initializable { /// would be a multiple of 50. uint256[45] private __gap; + /// @notice Emitted when an ETH bridge is initiated to the other chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of ETH sent. + /// @param extraData Extra data sent with the transaction. + event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); + + /// @notice Emitted when an ETH bridge is finalized on this chain. + /// @param from Address of the sender. + /// @param to Address of the receiver. + /// @param amount Amount of ETH sent. + /// @param extraData Extra data sent with the transaction. + event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); + /// @notice Emitted when an ERC20 bridge is initiated to the other chain. /// @param localToken Address of the ERC20 on this chain. /// @param remoteToken Address of the ERC20 on the remote chain. @@ -97,19 +117,19 @@ abstract contract StandardBridge is Initializable { /// @param _otherBridge Contract for the other StandardBridge contract. function __StandardBridge_init( CrossDomainMessenger _messenger, - StandardBridge _otherBridge, - address _l1USDC, - address _l2USDC + StandardBridge _otherBridge ) internal onlyInitializing { messenger = _messenger; otherBridge = _otherBridge; - l1USDC = _l1USDC; - l2USDC = _l2USDC; } + /// @notice Allows EOAs to bridge ETH by sending directly to the bridge. + /// Must be implemented by contracts that inherit. + receive() external payable virtual; + /// @notice Returns the address of the custom gas token and the token's decimals. function gasPayingToken() internal view virtual returns (address, uint8); @@ -143,6 +163,31 @@ abstract contract StandardBridge is Initializable { return false; } + /// @notice Sends ETH to the sender's address on the other chain. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA { + _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData); + } + + /// @notice Sends ETH to a receiver's address on the other chain. Note that if ETH is sent to a + /// smart contract and the call fails, the ETH will be temporarily locked in the + /// StandardBridge on the other chain until the call is replayed. If the call cannot be + /// replayed with any amount of gas (call always reverts), then the ETH will be + /// permanently locked in the StandardBridge on the other chain. ETH will also + /// be locked if the receiver is the other bridge, because finalizeBridgeETH will revert + /// in that case. + /// @param _to Address of the receiver. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable { + _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData); + } + /// @notice Sends ERC20 tokens to the sender's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -188,6 +233,38 @@ abstract contract StandardBridge is Initializable { _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData); } + /// @notice Finalizes an ETH bridge on this chain. Can only be triggered by the other + /// StandardBridge contract on the remote chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH being bridged. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function finalizeBridgeETH( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + public + payable + onlyOtherBridge + { + require(paused() == false, "StandardBridge: paused"); + require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); + require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); + require(_to != address(this), "StandardBridge: cannot send to self"); + require(_to != address(messenger), "StandardBridge: cannot send to messenger"); + + // Emit the correct events. By default this will be _amount, but child + // contracts may override this function in order to emit legacy events as well. + _emitETHBridgeFinalized(_from, _to, _amount, _extraData); + + bool success = SafeCall.call(_to, gasleft(), _amount, hex""); + require(success, "StandardBridge: ETH transfer failed"); + } + /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other /// StandardBridge contract on the remote chain. /// @param _localToken Address of the ERC20 on this chain. @@ -210,18 +287,54 @@ abstract contract StandardBridge is Initializable { onlyOtherBridge { require(paused() == false, "StandardBridge: paused"); - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; - IERC20(_localToken).safeTransfer(_to, _amount); + if (_isOptimismMintableERC20(_localToken)) { + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + + OptimismMintableERC20(_localToken).mint(_to, _amount); + } else { + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; + IERC20(_localToken).safeTransfer(_to, _amount); + } // Emit the correct events. By default this will be ERC20BridgeFinalized, but child // contracts may override this function in order to emit legacy events as well. _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } + /// @notice Initiates a bridge of ETH through the CrossDomainMessenger. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH being bridged. + /// @param _minGasLimit Minimum amount of gas that the bridge can be relayed with. + /// @param _extraData Extra data to be sent with the transaction. Note that the recipient will + /// not be triggered with this data, but it will be emitted and can be used + /// to identify the transaction. + function _initiateBridgeETH( + address _from, + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes memory _extraData + ) + internal + { + require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token"); + require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); + + // Emit the correct events. By default this will be _amount, but child + // contracts may override this function in order to emit legacy events as well. + _emitETHBridgeInitiated(_from, _to, _amount, _extraData); + + messenger.sendMessage{ value: _amount }({ + _target: address(otherBridge), + _message: abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData), + _minGasLimit: _minGasLimit + }); + } + /// @notice Sends ERC20 tokens to a receiver's address on the other chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. @@ -243,13 +356,18 @@ abstract contract StandardBridge is Initializable { internal { require(msg.value == 0, "StandardBridge: cannot send value"); - require(paused() == false, "StandardBridge: paused"); - require( - _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" - ); - IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); - deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; + + if (_isOptimismMintableERC20(_localToken)) { + require( + _isCorrectTokenPair(_localToken, _remoteToken), + "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + ); + + OptimismMintableERC20(_localToken).burn(_from, _amount); + } else { + IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); + deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; + } // Emit the correct events. By default this will be ERC20BridgeInitiated, but child // contracts may override this function in order to emit legacy events as well. @@ -273,19 +391,63 @@ abstract contract StandardBridge is Initializable { }); } - /** - * @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. - * Calls can be saved in the future by combining this logic with - * `_isOptimismMintableERC20`. - * - * @param _mintableToken OptimismMintableERC20 to check against. - * @param _otherToken Pair token to check. - * - * @return True if the other token is the correct pair token for the OptimismMintableERC20. - */ + /// @notice Checks if a given address is an OptimismMintableERC20. Not perfect, but good enough. + /// Just the way we like it. + /// @param _token Address of the token to check. + /// @return True if the token is an OptimismMintableERC20. + function _isOptimismMintableERC20(address _token) internal view returns (bool) { + return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId) + || ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId); + } + + /// @notice Checks if the "other token" is the correct pair token for the OptimismMintableERC20. + /// Calls can be saved in the future by combining this logic with + /// `_isOptimismMintableERC20`. + /// @param _mintableToken OptimismMintableERC20 to check against. + /// @param _otherToken Pair token to check. + /// @return True if the other token is the correct pair token for the OptimismMintableERC20. function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) { - return - ((_mintableToken == l1USDC && _otherToken == l2USDC) || (_mintableToken == l2USDC && _otherToken == l1USDC)); + if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) { + return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token(); + } else { + return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken(); + } + } + + /// @notice Emits the ETHBridgeInitiated event and if necessary the appropriate legacy event + /// when an ETH bridge is finalized on this chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH sent. + /// @param _extraData Extra data sent with the transaction. + function _emitETHBridgeInitiated( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /// @notice Emits the ETHBridgeFinalized and if necessary the appropriate legacy event when an + /// ETH bridge is finalized on this chain. + /// @param _from Address of the sender. + /// @param _to Address of the receiver. + /// @param _amount Amount of ETH sent. + /// @param _extraData Extra data sent with the transaction. + function _emitETHBridgeFinalized( + address _from, + address _to, + uint256 _amount, + bytes memory _extraData + ) + internal + virtual + { + emit ETHBridgeFinalized(_from, _to, _amount, _extraData); } /// @notice Emits the ERC20BridgeInitiated event and if necessary the appropriate legacy @@ -331,4 +493,4 @@ abstract contract StandardBridge is Initializable { { emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} +} \ No newline at end of file From bc5cfbb9389fc7aac7a5aec0f83bbe15a1bddcf6 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 11:04:24 +0200 Subject: [PATCH 06/15] iterate over src contracts --- .../src/L1/L1DedicatedBridge.sol | 112 +++++++++++------- .../src/L1/L1StandardBridge.sol | 2 +- .../src/L2/L2DedicatedBridge.sol | 47 ++++---- .../src/L2/L2StandardBridge.sol | 2 +- .../src/universal/DedicatedBridge.sol | 30 ++--- .../src/universal/StandardBridge.sol | 2 +- 6 files changed, 112 insertions(+), 83 deletions(-) diff --git a/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol index ba3aa9ffe73..36292b84bcd 100644 --- a/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { StandardBridge } from "src/universal/StandardBridge.sol"; +import { DedicatedBridge } from "src/universal/DedicatedBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; @@ -10,16 +10,12 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; /// @custom:proxied -/// @title L1StandardBridge -/// @notice The L1StandardBridge is responsible for transfering ETH and ERC20 tokens between L1 and +/// @title L1DedicatedBridge +/// @notice The L1DedicatedBridge is responsible for transfering a single ERC20 token between L1 and /// L2. In the case that an ERC20 token is native to L1, it will be escrowed within this -/// contract. If the ERC20 token is native to L2, it will be burnt. Before Bedrock, ETH was -/// stored within this contract. After Bedrock, ETH is instead stored inside the -/// OptimismPortal contract. -/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples -/// of some token types that may not be properly supported by this contract include, but are -/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. -contract L1StandardBridge is StandardBridge, ISemver { +/// contract. If the ERC20 token is native to L2, it will be burnt. +/// This contract is based on the L1StandardBridge contract. +contract L1DedicatedBridge is DedicatedBridge, ISemver { /// @custom:legacy /// @notice Emitted whenever an ERC20 deposit is initiated. /// @param l1Token Address of the token on L1. @@ -64,71 +60,66 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @notice Address of the SystemConfig contract. SystemConfig public systemConfig; - /// @notice The address of L1 USDC address. + /// @notice The address of L1 token. // solhint-disable-next-line var-name-mixedcase - address public immutable l1USDC; + address public immutable l1Token; - /// @notice The address of L2 USDC address. - address public immutable l2USDC; + /// @notice The address of L2 token. + address public immutable l2Token; - /// @notice Constructs the L1StandardBridge contract. + /// @notice Constructs the L1DedicatedBridge contract. constructor( - address _l1USDC, - address _l2USDC, CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, SystemConfig _systemConfig, - address _otherBridgeAddress + address _otherBridgeAddress, + address _l1Token, + address _l2Token ) - StandardBridge() + DedicatedBridge() { - l1USDC = _l1USDC; - l2USDC = _l2USDC; initialize({ _messenger: _messenger, _superchainConfig: _superchainConfig, _systemConfig: _systemConfig, - _otherBridgeAddress: _otherBridgeAddress + _otherBridgeAddress: _otherBridgeAddress, + _l1Token: _l1Token, + _l2Token: _l2Token }); } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. - /// @param _otherBridgeAddress Contract for the other StandardBridge contract. + /// @param _otherBridgeAddress Contract for the other DedicatedBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, SystemConfig _systemConfig, - address _otherBridgeAddress + address _otherBridgeAddress, + address _l1Token, + address _l2Token ) public initializer { superchainConfig = _superchainConfig; systemConfig = _systemConfig; - __StandardBridge_init({ _messenger: _messenger, _otherBridge: StandardBridge(payable(_otherBridgeAddress)) }); + __StandardBridge_init({ _messenger: _messenger, _otherBridge: DedicatedBridge(payable(_otherBridgeAddress)) }); + l1Token = _l1Token; + l2Token = _l2Token; } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function paused() public view override returns (bool) { return superchainConfig.paused(); } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = systemConfig.gasPayingToken(); } - /// @notice Burns all locked USDC if the pbridge is already paused - function burnAllLockedUSDC() external { - require(paused() == true, "Bridge should be paused before burning all locked USDC"); - require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); - // uint256 _balance = totalBridgedUSDC; - deposits[l1USDC][l2USDC] = 0; - // IERC20(l1USDC).burn(_balance); // check if this needs to be done - } - /// @custom:legacy /// @notice Deposits some amount of ERC20 tokens into the sender's account on L2. /// @param _l1Token Address of the L1 token being deposited. @@ -198,6 +189,47 @@ contract L1StandardBridge is StandardBridge, ISemver { // warnning check there is no reentrancy } + /// @notice Deposits some amount of l1Token tokens into the sender's account on L2. + /// A convenience function which does not require the extra token parameters. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositL1Token(uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external virtual { + _initiateERC20Deposit(l1Token, l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); + } + + /// @notice Deposits some amount of l1Token tokens into a target account on L2. + /// A convenience function which does not require the extra token parameters. + /// @param _to Address of the recipient on L2. + /// @param _amount Amount of the ERC20 to deposit. + /// @param _minGasLimit Minimum gas limit for the deposit message on L2. + /// @param _extraData Optional data to forward to L2. + /// Data supplied here will not be used to execute any code on L2 and is + /// only emitted as extra data for the convenience of off-chain tooling. + function depositL1TokenTo( + address _to, + uint256 _amount, + uint32 _minGasLimit, + bytes calldata _extraData + ) + external + virtual + { + _initiateERC20Deposit(l1Token, l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); + } + + /// @notice Finalizes a withdrawal of l2Token tokens from L2. + /// A convenience function which does not require the extra token parameters. + /// @param _from Address of the withdrawer on L2. + /// @param _to Address of the recipient on L1. + /// @param _amount Amount of the ERC20 to withdraw. + /// @param _extraData Optional data forwarded from L2. + function finalizeL2TokenWithdrawal(address _from, address _to, uint256 _amount, bytes calldata _extraData) external { + finalizeBridgeERC20(l1Token, l2Token, _from, _to, _amount, _extraData); + } + /// @custom:legacy /// @notice Retrieves the access of the corresponding L2 bridge contract. /// @return Address of the corresponding L2 bridge contract. @@ -227,12 +259,12 @@ contract L1StandardBridge is StandardBridge, ISemver { _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData); } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view override returns (bool) { - return (_mintableToken == l1USDC && _otherToken == l2USDC); + return (_mintableToken == l1Token && _otherToken == l2Token); } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized /// event. This is necessary for backwards compatibility with the legacy bridge. function _emitERC20BridgeInitiated( @@ -250,7 +282,7 @@ contract L1StandardBridge is StandardBridge, ISemver { super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge /// @notice Emits the legacy ERC20WithdrawalFinalized event followed by the ERC20BridgeFinalized /// event. This is necessary for backwards compatibility with the legacy bridge. function _emitERC20BridgeFinalized( diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 3d1489f6b63..757c140c56e 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -339,4 +339,4 @@ contract L1StandardBridge is StandardBridge, ISemver { emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} \ No newline at end of file +} diff --git a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol index 9b59ef62591..c6a228003b0 100644 --- a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol @@ -2,21 +2,19 @@ pragma solidity 0.8.15; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { StandardBridge } from "src/universal/StandardBridge.sol"; +import { DedicatedBridge } from "src/universal/DedicatedBridge.sol"; import { ISemver } from "src/universal/ISemver.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { L1Block } from "src/L2/L1Block.sol"; /// @custom:proxied /// @custom:predeploy 0x4200000000000000000000000000000000000010 -/// @title L2StandardBridge -/// @notice The L2StandardBridge is responsible for transfering ETH and ERC20 tokens between L1 and +/// @title L2DedicatedBridge +/// @notice The L2DedicatedBridge is responsible for transfering a single ERC20 tokens between L1 and /// L2. In the case that an ERC20 token is native to L2, it will be escrowed within this /// contract. If the ERC20 token is native to L1, it will be burnt. -/// NOTE: this contract is not intended to support all variations of ERC20 tokens. Examples -/// of some token types that may not be properly supported by this contract include, but are -/// not limited to: tokens with transfer fees, rebasing tokens, and tokens with blocklists. -contract L2StandardBridge is StandardBridge, ISemver { +/// This contract is based on the L2StandardBridge contract. +contract L2DedicatedBridge is DedicatedBridge, ISemver { /// @custom:legacy /// @notice Emitted whenever a withdrawal from L2 to L1 is initiated. /// @param l1Token Address of the token on L1. @@ -54,30 +52,30 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @custom:semver 1.10.0 string public constant version = "1.10.0"; - /// @notice The address of L1 USDC address. + /// @notice The address of L1 token address. // solhint-disable-next-line var-name-mixedcase - address public immutable l1USDC; + address public immutable l1Token; - /// @notice The address of L2 USDC address. - address public immutable l2USDC; + /// @notice The address of L2 token address. + address public immutable l2Token; - /// @notice Constructs the L2StandardBridge contract. - constructor(address _l1USDC, address _l2USDC) StandardBridge() { - initialize({ _otherBridge: StandardBridge(payable(address(0))) }); - l1USDC = _l1USDC; - l2USDC = _l2USDC; + /// @notice Constructs the L2DedicatedBridge contract. + constructor(address _l1Token, address _l2Token) DedicatedBridge() { + initialize({ _otherBridge: DedicatedBridge(payable(address(0))), _l1Token: _l1Token, _l2Token: _l2Token }); } /// @notice Initializer. /// @param _otherBridge Contract for the corresponding bridge on the other chain. - function initialize(StandardBridge _otherBridge) public initializer { + function initialize(DedicatedBridge _otherBridge, address _l1Token, address _l2Token) public initializer { __StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), _otherBridge: _otherBridge }); + l1Token = _l1Token; + l2Token = _l2Token; } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) { (addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken(); } @@ -100,7 +98,7 @@ contract L2StandardBridge is StandardBridge, ISemver { virtual onlyEOA { - require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); + require(isCustomGasToken() == false, "L2DedicatedBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); } @@ -123,7 +121,7 @@ contract L2StandardBridge is StandardBridge, ISemver { payable virtual { - require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token"); + require(isCustomGasToken() == false, "L2DedicatedBridge: not supported with custom gas token"); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); } @@ -152,18 +150,17 @@ contract L2StandardBridge is StandardBridge, ISemver { ) internal { - address l1Token = l1USDC; _initiateBridgeERC20(_l2Token, l1Token, _from, _to, _amount, _minGasLimit, _extraData); } - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view override returns (bool) { - return (_mintableToken == l2USDC && _otherToken == l1USDC); + return (_mintableToken == l2Token && _otherToken == l1Token); } /// @notice Emits the legacy WithdrawalInitiated event followed by the ERC20BridgeInitiated /// event. This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function _emitERC20BridgeInitiated( address _localToken, address _remoteToken, @@ -181,7 +178,7 @@ contract L2StandardBridge is StandardBridge, ISemver { /// @notice Emits the legacy DepositFinalized event followed by the ERC20BridgeFinalized event. /// This is necessary for backwards compatibility with the legacy bridge. - /// @inheritdoc StandardBridge + /// @inheritdoc DedicatedBridge function _emitERC20BridgeFinalized( address _localToken, address _remoteToken, diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 8946274f5b2..1472d0fd9e8 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -234,4 +234,4 @@ contract L2StandardBridge is StandardBridge, ISemver { emit DepositFinalized(_remoteToken, _localToken, _from, _to, _amount, _extraData); super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} \ No newline at end of file +} diff --git a/packages/contracts-bedrock/src/universal/DedicatedBridge.sol b/packages/contracts-bedrock/src/universal/DedicatedBridge.sol index 3b0c2854537..9999e59ff40 100644 --- a/packages/contracts-bedrock/src/universal/DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/universal/DedicatedBridge.sol @@ -10,11 +10,11 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable import { Constants } from "src/libraries/Constants.sol"; /// @custom:upgradeable -/// @title StandardBridge -/// @notice StandardBridge is a base contract for the L1 and L2 standard ERC20 bridges. It handles +/// @title DedicatedBridge +/// @notice DedicatedBridge is a base contract for the L1 and L2 dedicated ERC20 bridges. It handles /// the core bridging logic, including escrowing tokens that are native to the local chain /// and minting/burning tokens that are native to the remote chain. -abstract contract StandardBridge is Initializable { +abstract contract DedicatedBridge is Initializable { using SafeERC20 for IERC20; /// @notice The L2 gas limit set when eth is depoisited using the receive() function. @@ -29,7 +29,7 @@ abstract contract StandardBridge is Initializable { /// @notice Corresponding bridge on the other domain. /// @custom:network-specific - StandardBridge public otherBridge; + DedicatedBridge public otherBridge; /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. /// A gap size of 45 was chosen here, so that the first slot used in a child contract @@ -72,7 +72,7 @@ abstract contract StandardBridge is Initializable { /// calling code within their constructors, but also doesn't really matter since we're /// just trying to prevent users accidentally depositing with smart contract wallets. modifier onlyEOA() { - require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA"); + require(!Address.isContract(msg.sender), "DedicatedBridge: function can only be called from an EOA"); _; } @@ -80,17 +80,17 @@ abstract contract StandardBridge is Initializable { modifier onlyOtherBridge() { require( msg.sender == address(messenger) && messenger.xDomainMessageSender() == address(otherBridge), - "StandardBridge: function can only be called from the other bridge" + "DedicatedBridge: function can only be called from the other bridge" ); _; } /// @notice Initializer. /// @param _messenger Contract for CrossDomainMessenger on this network. - /// @param _otherBridge Contract for the other StandardBridge contract. + /// @param _otherBridge Contract for the other DedicatedBridge contract. function __StandardBridge_init( CrossDomainMessenger _messenger, - StandardBridge _otherBridge + DedicatedBridge _otherBridge ) internal onlyInitializing @@ -120,7 +120,7 @@ abstract contract StandardBridge is Initializable { /// Public getter is legacy and will be removed in the future. Use `otherBridge` instead. /// @return Contract of the bridge on the other network. /// @custom:legacy - function OTHER_BRIDGE() external view returns (StandardBridge) { + function OTHER_BRIDGE() external view returns (DedicatedBridge) { return otherBridge; } @@ -178,7 +178,7 @@ abstract contract StandardBridge is Initializable { } /// @notice Finalizes an ERC20 bridge on this chain. Can only be triggered by the other - /// StandardBridge contract on the remote chain. + /// DedicatedBridge contract on the remote chain. /// @param _localToken Address of the ERC20 on this chain. /// @param _remoteToken Address of the corresponding token on the remote chain. /// @param _from Address of the sender. @@ -198,10 +198,10 @@ abstract contract StandardBridge is Initializable { public onlyOtherBridge { - require(paused() == false, "StandardBridge: paused"); + require(paused() == false, "DedicatedBridge: paused"); require( _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + "DedicatedBridge: wrong remote token for Optimism Mintable ERC20 local token" ); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount; IERC20(_localToken).safeTransfer(_to, _amount); @@ -231,11 +231,11 @@ abstract contract StandardBridge is Initializable { ) internal { - require(msg.value == 0, "StandardBridge: cannot send value"); - require(paused() == false, "StandardBridge: paused"); + require(msg.value == 0, "DedicatedBridge: cannot send value"); + require(paused() == false, "DedicatedBridge: paused"); require( _isCorrectTokenPair(_localToken, _remoteToken), - "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token" + "DedicatedBridge: wrong remote token for Optimism Mintable ERC20 local token" ); IERC20(_localToken).safeTransferFrom(_from, address(this), _amount); deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount; diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index bbcb9e8e492..140aba531e6 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -493,4 +493,4 @@ abstract contract StandardBridge is Initializable { { emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); } -} \ No newline at end of file +} From 8b6b6eef6826027a76fbc9a3bbd4524dda3f270b Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 11:04:50 +0200 Subject: [PATCH 07/15] iterate over src contracts --- .../src/L1/L1DedicatedUSDCBridge.sol | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol diff --git a/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol b/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol new file mode 100644 index 00000000000..9c8f89be5f2 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; +import { ISemver } from "src/universal/ISemver.sol"; +import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { OptimismPortal } from "src/L1/OptimismPortal.sol"; +import { SystemConfig } from "src/L1/SystemConfig.sol"; + +/// @custom:proxied +/// @title L1DedicatedUSDCBridge +/// @notice The L1DedicatedUSDCBridge is responsible for transfering the USDC token between L1 and +/// L2. +contract L1DedicatedUSDCBridge is L1DedicatedBridge { + /// @notice Burns all locked USDC if the bridge is already paused + function burnAllLockedUSDC() external { + require(paused() == true, "Bridge should be paused before burning all locked USDC"); + require(msg.sender == superchainConfig.guardian(), "SuperchainConfig: only guardian can burn all USDC"); + deposits[l1Token][l2Token] = 0; + // IERC20(l1USDC).burn(_balance); // check if this needs to be done + } +} From 70e2495cc4a0157771cf79632d1c1249aba85fa9 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 12:41:21 +0200 Subject: [PATCH 08/15] deployment script compiles --- .../scripts/ChainAssertions.sol | 1 + .../src/L1/L1DedicatedBridge.sol | 42 +++++++++---------- .../src/L2/L2DedicatedBridge.sol | 5 +-- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/contracts-bedrock/scripts/ChainAssertions.sol b/packages/contracts-bedrock/scripts/ChainAssertions.sol index a99c14e9514..35a2fd171de 100644 --- a/packages/contracts-bedrock/scripts/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/ChainAssertions.sol @@ -8,6 +8,7 @@ import { Deployer } from "scripts/Deployer.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { Constants } from "src/libraries/Constants.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; +import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; diff --git a/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol index 36292b84bcd..93c864c9295 100644 --- a/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1DedicatedBridge.sol @@ -62,41 +62,32 @@ contract L1DedicatedBridge is DedicatedBridge, ISemver { /// @notice The address of L1 token. // solhint-disable-next-line var-name-mixedcase - address public immutable l1Token; + address public l1Token; /// @notice The address of L2 token. - address public immutable l2Token; + address public l2Token; /// @notice Constructs the L1DedicatedBridge contract. - constructor( - CrossDomainMessenger _messenger, - SuperchainConfig _superchainConfig, - SystemConfig _systemConfig, - address _otherBridgeAddress, - address _l1Token, - address _l2Token - ) - DedicatedBridge() - { + constructor() DedicatedBridge() { initialize({ - _messenger: _messenger, - _superchainConfig: _superchainConfig, - _systemConfig: _systemConfig, - _otherBridgeAddress: _otherBridgeAddress, - _l1Token: _l1Token, - _l2Token: _l2Token + _messenger: CrossDomainMessenger(address(0)), + _superchainConfig: SuperchainConfig(address(0)), + _systemConfig: SystemConfig(address(0)), + _otherBridge: address(0), + _l1Token: address(0), + _l2Token: address(0) }); } /// @notice Initializer. /// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network. - /// @param _otherBridgeAddress Contract for the other DedicatedBridge contract. + /// @param _otherBridge Contract for the other DedicatedBridge contract. function initialize( CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig, SystemConfig _systemConfig, - address _otherBridgeAddress, + address _otherBridge, address _l1Token, address _l2Token ) @@ -105,7 +96,7 @@ contract L1DedicatedBridge is DedicatedBridge, ISemver { { superchainConfig = _superchainConfig; systemConfig = _systemConfig; - __StandardBridge_init({ _messenger: _messenger, _otherBridge: DedicatedBridge(payable(_otherBridgeAddress)) }); + __StandardBridge_init({ _messenger: _messenger, _otherBridge: DedicatedBridge(payable(_otherBridge)) }); l1Token = _l1Token; l2Token = _l2Token; } @@ -226,7 +217,14 @@ contract L1DedicatedBridge is DedicatedBridge, ISemver { /// @param _to Address of the recipient on L1. /// @param _amount Amount of the ERC20 to withdraw. /// @param _extraData Optional data forwarded from L2. - function finalizeL2TokenWithdrawal(address _from, address _to, uint256 _amount, bytes calldata _extraData) external { + function finalizeL2TokenWithdrawal( + address _from, + address _to, + uint256 _amount, + bytes calldata _extraData + ) + external + { finalizeBridgeERC20(l1Token, l2Token, _from, _to, _amount, _extraData); } diff --git a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol index c6a228003b0..ef3e1085bcb 100644 --- a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol @@ -8,7 +8,6 @@ import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { L1Block } from "src/L2/L1Block.sol"; /// @custom:proxied -/// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @title L2DedicatedBridge /// @notice The L2DedicatedBridge is responsible for transfering a single ERC20 tokens between L1 and /// L2. In the case that an ERC20 token is native to L2, it will be escrowed within this @@ -54,10 +53,10 @@ contract L2DedicatedBridge is DedicatedBridge, ISemver { /// @notice The address of L1 token address. // solhint-disable-next-line var-name-mixedcase - address public immutable l1Token; + address public l1Token; /// @notice The address of L2 token address. - address public immutable l2Token; + address public l2Token; /// @notice Constructs the L2DedicatedBridge contract. constructor(address _l1Token, address _l2Token) DedicatedBridge() { From b99db0c318e50d2ddb547c696f6736b1b638da92 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 13:22:19 +0200 Subject: [PATCH 09/15] succesful L1 deployment --- packages/contracts-bedrock/scripts/Config.sol | 2 +- packages/contracts-bedrock/scripts/DeployConfig.s.sol | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/Config.sol b/packages/contracts-bedrock/scripts/Config.sol index 0bf567b3dd1..c4e7c8809e2 100644 --- a/packages/contracts-bedrock/scripts/Config.sol +++ b/packages/contracts-bedrock/scripts/Config.sol @@ -94,7 +94,7 @@ library Config { /// @notice The CREATE2 salt to be used when deploying the implementations. function implSalt() internal view returns (string memory _env) { - _env = vm.envOr("IMPL_SALT", string("ethers phoenix")); + _env = vm.envOr("IMPL_SALT", string("ethers phoenix sergey")); } /// @notice Returns the path that the state dump file should be written to or read from diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index 25869e97f08..a62238a2bf4 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -91,6 +91,9 @@ contract DeployConfig is Script { bool public useInterop; + address public systemConfigProxy; + address public l1CrossDomainMessengerProxy; + function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); try vm.readFile(_path) returns (string memory data) { @@ -174,6 +177,10 @@ contract DeployConfig is Script { customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0)); useInterop = _readOr(_json, "$.useInterop", false); + + l1CrossDomainMessengerProxy = stdJson.readAddress(_json, "$.l1CrossDomainMessengerProxy"); + systemConfigProxy = stdJson.readAddress(_json, "$.systemConfigProxy"); + } function fork() public view returns (Fork fork_) { From 2554191a78e8d6dceb47e15c37cb41d09ec37fc0 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 14:59:11 +0200 Subject: [PATCH 10/15] deployment script completed --- packages/contracts-bedrock/scripts/DeployConfig.s.sol | 1 - packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index a62238a2bf4..4b273141d56 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -180,7 +180,6 @@ contract DeployConfig is Script { l1CrossDomainMessengerProxy = stdJson.readAddress(_json, "$.l1CrossDomainMessengerProxy"); systemConfigProxy = stdJson.readAddress(_json, "$.systemConfigProxy"); - } function fork() public view returns (Fork fork_) { diff --git a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol index ef3e1085bcb..0f862304a46 100644 --- a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol @@ -59,16 +59,16 @@ contract L2DedicatedBridge is DedicatedBridge, ISemver { address public l2Token; /// @notice Constructs the L2DedicatedBridge contract. - constructor(address _l1Token, address _l2Token) DedicatedBridge() { - initialize({ _otherBridge: DedicatedBridge(payable(address(0))), _l1Token: _l1Token, _l2Token: _l2Token }); + constructor(address _otherBridge, address _l1Token, address _l2Token) DedicatedBridge() { + initialize({ _otherBridge: _otherBridge, _l1Token: _l1Token, _l2Token: _l2Token }); } /// @notice Initializer. /// @param _otherBridge Contract for the corresponding bridge on the other chain. - function initialize(DedicatedBridge _otherBridge, address _l1Token, address _l2Token) public initializer { + function initialize(address _otherBridge, address _l1Token, address _l2Token) public initializer { __StandardBridge_init({ _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER), - _otherBridge: _otherBridge + _otherBridge: DedicatedBridge(payable(_otherBridge)) }); l1Token = _l1Token; l2Token = _l2Token; From 6043934e380424aa7b998670aeed93efa464e281 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 17:21:15 +0200 Subject: [PATCH 11/15] finalize deploy script --- packages/contracts-bedrock/scripts/Config.sol | 2 +- packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol | 6 ------ packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/contracts-bedrock/scripts/Config.sol b/packages/contracts-bedrock/scripts/Config.sol index c4e7c8809e2..938919a22e8 100644 --- a/packages/contracts-bedrock/scripts/Config.sol +++ b/packages/contracts-bedrock/scripts/Config.sol @@ -94,7 +94,7 @@ library Config { /// @notice The CREATE2 salt to be used when deploying the implementations. function implSalt() internal view returns (string memory _env) { - _env = vm.envOr("IMPL_SALT", string("ethers phoenix sergey")); + _env = vm.envOr("IMPL_SALT", string("ethers phoenix sergio")); } /// @notice Returns the path that the state dump file should be written to or read from diff --git a/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol b/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol index 9c8f89be5f2..e7b1d09bc55 100644 --- a/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1DedicatedUSDCBridge.sol @@ -1,13 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; -import { ISemver } from "src/universal/ISemver.sol"; -import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; -import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; -import { OptimismPortal } from "src/L1/OptimismPortal.sol"; -import { SystemConfig } from "src/L1/SystemConfig.sol"; /// @custom:proxied /// @title L1DedicatedUSDCBridge diff --git a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol index 0f862304a46..287ad387ddf 100644 --- a/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2DedicatedBridge.sol @@ -59,8 +59,8 @@ contract L2DedicatedBridge is DedicatedBridge, ISemver { address public l2Token; /// @notice Constructs the L2DedicatedBridge contract. - constructor(address _otherBridge, address _l1Token, address _l2Token) DedicatedBridge() { - initialize({ _otherBridge: _otherBridge, _l1Token: _l1Token, _l2Token: _l2Token }); + constructor() DedicatedBridge() { + initialize({ _otherBridge: address(0), _l1Token: address(0), _l2Token: address(0) }); } /// @notice Initializer. From 9cca438158e33376477e74915d45d8b3b3983cf5 Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 17:21:39 +0200 Subject: [PATCH 12/15] add deployment script --- .../scripts/DeployDedicatedBridge.s.sol | 553 ++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol diff --git a/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol b/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol new file mode 100644 index 00000000000..7fa922a88e8 --- /dev/null +++ b/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { VmSafe } from "forge-std/Vm.sol"; +import { Script } from "forge-std/Script.sol"; + +import { console2 as console } from "forge-std/console2.sol"; +import { stdJson } from "forge-std/StdJson.sol"; + +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; +import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; +import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; +import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; + +import { Deployer } from "scripts/Deployer.sol"; + +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; +import { AddressManager } from "src/legacy/AddressManager.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { DedicatedBridge } from "src/universal/DedicatedBridge.sol"; +import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; +import { L1DedicatedUSDCBridge } from "src/L1/L1DedicatedUSDCBridge.sol"; +import { L2DedicatedBridge } from "src/L2/L2DedicatedBridge.sol"; +import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; +import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { SystemConfig } from "src/L1/SystemConfig.sol"; +import { SystemConfigInterop } from "src/L1/SystemConfigInterop.sol"; +import { ResourceMetering } from "src/L1/ResourceMetering.sol"; +import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol"; +import { Constants } from "src/libraries/Constants.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; +import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; +import { StorageSetter } from "src/universal/StorageSetter.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { Proxy } from "src/universal/Proxy.sol"; + +import { Chains } from "scripts/Chains.sol"; +import { Config } from "scripts/Config.sol"; + +import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; +import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; +import "src/dispute/lib/Types.sol"; +import { ChainAssertions } from "scripts/ChainAssertions.sol"; +import { Types } from "scripts/Types.sol"; +import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; +import { Process } from "scripts/libraries/Process.sol"; + +/// @title Deploy +/// @notice Script used to deploy a dedicated bridge. +contract DeployDedicatedBridge is Deployer { + using stdJson for string; + + //////////////////////////////////////////////////////////////// + // Modifiers // + //////////////////////////////////////////////////////////////// + + /// @notice Modifier that wraps a function in broadcasting. + modifier broadcast() { + vm.startBroadcast(msg.sender); + _; + vm.stopBroadcast(); + } + + /// @notice Modifier that will only allow a function to be called on devnet. + modifier onlyDevnet() { + uint256 chainid = block.chainid; + if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) { + _; + } + } + + /// @notice Modifier that will only allow a function to be called on a public + /// testnet or devnet. + modifier onlyTestnetOrDevnet() { + uint256 chainid = block.chainid; + if ( + chainid == Chains.Goerli || chainid == Chains.Sepolia || chainid == Chains.LocalDevnet + || chainid == Chains.GethDevnet + ) { + _; + } + } + + /// @notice Modifier that wraps a function with statediff recording. + /// The returned AccountAccess[] array is then written to + /// the `snapshots/state-diff/.json` output file. + modifier stateDiff() { + vm.startStateDiffRecording(); + _; + VmSafe.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); + console.log( + "Writing %d state diff account accesses to snapshots/state-diff/%s.json", + accesses.length, + vm.toString(block.chainid) + ); + string memory json = LibStateDiff.encodeAccountAccesses(accesses); + string memory statediffPath = + string.concat(vm.projectRoot(), "/snapshots/state-diff/", vm.toString(block.chainid), ".json"); + vm.writeJson({ json: json, path: statediffPath }); + } + + //////////////////////////////////////////////////////////////// + // Accessors // + //////////////////////////////////////////////////////////////// + + /// @notice The create2 salt used for deployment of the contract implementations. + /// Using this helps to reduce config across networks as the implementation + /// addresses will be the same across networks when deployed with create2. + function _implSalt() internal view returns (bytes32) { + return keccak256(bytes(Config.implSalt())); + } + + /// @notice Returns the proxy addresses. If a proxy is not found, it will have address(0). + function _proxies() internal view returns (Types.ContractSet memory proxies_) { + proxies_ = Types.ContractSet({ + L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"), + L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"), + L2OutputOracle: mustGetAddress("L2OutputOracleProxy"), + DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), + DelayedWETH: mustGetAddress("DelayedWETHProxy"), + AnchorStateRegistry: mustGetAddress("AnchorStateRegistryProxy"), + OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), + OptimismPortal: mustGetAddress("OptimismPortalProxy"), + OptimismPortal2: mustGetAddress("OptimismPortalProxy"), + SystemConfig: mustGetAddress("SystemConfigProxy"), + L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"), + ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"), + SuperchainConfig: mustGetAddress("SuperchainConfigProxy") + }); + } + + /// @notice Returns the proxy addresses, not reverting if any are unset. + function _proxiesUnstrict() internal view returns (Types.ContractSet memory proxies_) { + proxies_ = Types.ContractSet({ + L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"), + L1StandardBridge: getAddress("L1StandardBridgeProxy"), + L2OutputOracle: getAddress("L2OutputOracleProxy"), + DisputeGameFactory: getAddress("DisputeGameFactoryProxy"), + DelayedWETH: getAddress("DelayedWETHProxy"), + AnchorStateRegistry: getAddress("AnchorStateRegistryProxy"), + OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"), + OptimismPortal: getAddress("OptimismPortalProxy"), + OptimismPortal2: getAddress("OptimismPortalProxy"), + SystemConfig: getAddress("SystemConfigProxy"), + L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), + ProtocolVersions: getAddress("ProtocolVersionsProxy"), + SuperchainConfig: getAddress("SuperchainConfigProxy") + }); + } + + //////////////////////////////////////////////////////////////// + // State Changing Helper Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Gets the address of the SafeProxyFactory and Safe singleton for use in deploying a new GnosisSafe. + function _getSafeFactory() internal returns (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_) { + if (getAddress("SafeProxyFactory") != address(0)) { + // The SafeProxyFactory is already saved, we can just use it. + safeProxyFactory_ = SafeProxyFactory(getAddress("SafeProxyFactory")); + safeSingleton_ = Safe(getAddress("SafeSingleton")); + return (safeProxyFactory_, safeSingleton_); + } + + // These are the standard create2 deployed contracts. First we'll check if they are deployed, + // if not we'll deploy new ones, though not at these addresses. + address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + + safeProxyFactory.code.length == 0 + ? safeProxyFactory_ = new SafeProxyFactory() + : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory); + + safeSingleton.code.length == 0 ? safeSingleton_ = new Safe() : safeSingleton_ = Safe(payable(safeSingleton)); + + save("SafeProxyFactory", address(safeProxyFactory_)); + save("SafeSingleton", address(safeSingleton_)); + } + + /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data + function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { + // This is the signature format used the caller is also the signer. + bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); + + _safe.execTransaction({ + to: _target, + value: 0, + data: _data, + operation: SafeOps.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: signature + }); + } + + /// @notice Call from the Safe contract to the Proxy Admin's upgrade and call method + function _upgradeAndCallViaSafe(address _proxy, address _implementation, bytes memory _innerCallData) internal { + address proxyAdmin = mustGetAddress("ProxyAdmin"); + + bytes memory data = + abi.encodeCall(ProxyAdmin.upgradeAndCall, (payable(_proxy), _implementation, _innerCallData)); + + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + _callViaSafe({ _safe: safe, _target: proxyAdmin, _data: data }); + } + + /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner + function transferProxyAdminOwnership() public { + ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); + address owner = proxyAdmin.owner(); + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + proxyAdmin.transferOwnership(safe); + console.log("ProxyAdmin ownership transferred to Safe at: %s", safe); + } + } + + //////////////////////////////////////////////////////////////// + // High Level Deployment Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Deploy L1 dedicated bridge + function runL1DedicatedBridgeDeployment(address _otherBridge, address _l1Token, address _l2Token) public { + console.log("Deploying L1 Dedicated bridge"); + + deploySafe("SystemOwnerSafe"); + + // Deploy a new ProxyAdmin and AddressManager + // This proxy will be used on the SuperchainConfig and ProtocolVersions contracts, as well as the contracts + // in the OP Chain system. + deployAddressManager(); + deployProxyAdmin(); + transferProxyAdminOwnership(); + + // Deploy the SuperchainConfigProxy + deployERC1967Proxy("SuperchainConfigProxy"); + deploySuperchainConfig(); + initializeSuperchainConfig(); + + deployL1DedicatedBridgeProxy(); + transferAddressManagerOwnership(); // to the ProxyAdmin + deployL1DedicatedBridge(); + initializeL1DedicatedBridge(_otherBridge, _l1Token, _l2Token); + console.log("Done!"); + } + + // /// @notice Deploy the L1DedicatedBridge + // function deployAndInitializeL2DedicatedBridge( + // address _proxy, + // address _otherBridge, + // address _l1Token, + // address _l2Token + // ) + // public + // returns (address addr_) + // { + // addr_ = deployL2DedicatedBridge(); + // initializeL2DedicatedBridge(_proxy, _otherBridge, _l1Token, _l2Token); + + // addr_; + // } + + //////////////////////////////////////////////////////////////// + // Non-Proxied Deployment Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Deploy the Safe + function deploySafe(string memory _name) public returns (address addr_) { + address[] memory owners = new address[](0); + addr_ = deploySafe(_name, owners, 1, true); + } + + /// @notice Deploy a new Safe contract. If the keepDeployer option is used to enable further setup actions, then + /// the removeDeployerFromSafe() function should be called on that safe after setup is complete. + /// Note this function does not have the broadcast modifier. + /// @param _name The name of the Safe to deploy. + /// @param _owners The owners of the Safe. + /// @param _threshold The threshold of the Safe. + /// @param _keepDeployer Wether or not the deployer address will be added as an owner of the Safe. + function deploySafe( + string memory _name, + address[] memory _owners, + uint256 _threshold, + bool _keepDeployer + ) + public + broadcast + returns (address addr_) + { + bytes32 salt = keccak256(abi.encode(_name, _implSalt())); + console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); + (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); + + address[] memory expandedOwners = new address[](_owners.length + 1); + if (_keepDeployer) { + // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it + // easier to call removeOwner later. + expandedOwners[0] = msg.sender; + for (uint256 i = 0; i < _owners.length; i++) { + expandedOwners[i + 1] = _owners[i]; + } + _owners = expandedOwners; + } + + bytes memory initData = abi.encodeCall( + Safe.setup, (_owners, _threshold, address(0), hex"", address(0), address(0), 0, payable(address(0))) + ); + addr_ = address(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, uint256(salt))); + + save(_name, addr_); + console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, addr_); + } + + /// @notice If the keepDeployer option was used with deploySafe(), this function can be used to remove the deployer. + /// Note this function does not have the broadcast modifier. + function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public { + Safe safe = Safe(mustGetAddress(_name)); + + // The sentinel address is used to mark the start and end of the linked list of owners in the Safe. + address sentinelOwners = address(0x1); + + // Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner + // will be sentinelOwners. + _callViaSafe({ + _safe: safe, + _target: address(safe), + _data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold)) + }); + console.log("Removed deployer owner from ", _name); + } + + /// @notice Deploy the AddressManager + function deployAddressManager() public broadcast returns (address addr_) { + console.log("Deploying AddressManager"); + AddressManager manager = new AddressManager(); + require(manager.owner() == msg.sender); + + save("AddressManager", address(manager)); + console.log("AddressManager deployed at %s", address(manager)); + addr_ = address(manager); + } + + /// @notice Deploy the ProxyAdmin + function deployProxyAdmin() public broadcast returns (address addr_) { + console.log("Deploying ProxyAdmin"); + ProxyAdmin admin = new ProxyAdmin({ _owner: msg.sender }); + require(admin.owner() == msg.sender); + + AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); + if (admin.addressManager() != addressManager) { + admin.setAddressManager(addressManager); + } + + require(admin.addressManager() == addressManager); + + save("ProxyAdmin", address(admin)); + console.log("ProxyAdmin deployed at %s", address(admin)); + addr_ = address(admin); + } + + /// @notice Deploy the StorageSetter contract, used for upgrades. + function deployStorageSetter() public broadcast returns (address addr_) { + console.log("Deploying StorageSetter"); + StorageSetter setter = new StorageSetter{ salt: _implSalt() }(); + console.log("StorageSetter deployed at: %s", address(setter)); + string memory version = setter.version(); + console.log("StorageSetter version: %s", version); + addr_ = address(setter); + } + + //////////////////////////////////////////////////////////////// + // Proxy Deployment Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Deploy the L1DedicatedBridgeProxy using a ChugSplashProxy + function deployL1DedicatedBridgeProxy() public broadcast returns (address addr_) { + console.log("Deploying proxy for L1DedicatedUSDCBridge"); + address proxyAdmin = mustGetAddress("ProxyAdmin"); + L1ChugSplashProxy proxy = new L1ChugSplashProxy(proxyAdmin); + + require(EIP1967Helper.getAdmin(address(proxy)) == proxyAdmin); + + save("L1DedicatedBridgeProxy", address(proxy)); + console.log("L1DedicatedBridgeProxy deployed at %s", address(proxy)); + addr_ = address(proxy); + } + + /// @notice Deploy the L2DedicatedBridgeProxy using a Proxy + function deployL2DedicatedBridgeProxy() public returns (address addr_) { + console.log("Deploying proxy for L2DedicatedBridge"); + addr_ = deployERC1967ProxyWithOwner("L2DedicatedBridgeProxy", msg.sender); + } + + /// @notice Deploys an ERC1967Proxy contract with the ProxyAdmin as the owner. + /// @param _name The name of the proxy contract to be deployed. + /// @return addr_ The address of the deployed proxy contract. + function deployERC1967Proxy(string memory _name) public broadcast returns (address addr_) { + addr_ = deployERC1967ProxyWithOwner(_name, mustGetAddress("ProxyAdmin")); + } + + /// @notice Deploys an ERC1967Proxy contract with a specified owner. + /// @param _name The name of the proxy contract to be deployed. + /// @param _proxyOwner The address of the owner of the proxy contract. + /// @return addr_ The address of the deployed proxy contract. + function deployERC1967ProxyWithOwner( + string memory _name, + address _proxyOwner + ) + public + broadcast + returns (address addr_) + { + console.log(string.concat("Deploying ERC1967 proxy for ", _name)); + Proxy proxy = new Proxy({ _admin: _proxyOwner }); + + require(EIP1967Helper.getAdmin(address(proxy)) == _proxyOwner); + + save(_name, address(proxy)); + console.log(" at %s", address(proxy)); + addr_ = address(proxy); + } + + //////////////////////////////////////////////////////////////// + // Implementation Deployment Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Deploy the SuperchainConfig contract + function deploySuperchainConfig() public broadcast { + SuperchainConfig superchainConfig = new SuperchainConfig{ salt: _implSalt() }(); + + require(superchainConfig.guardian() == address(0)); + bytes32 initialized = vm.load(address(superchainConfig), bytes32(0)); + require(initialized != 0); + + save("SuperchainConfig", address(superchainConfig)); + console.log("SuperchainConfig deployed at %s", address(superchainConfig)); + } + + /// @notice Transfer ownership of the address manager to the ProxyAdmin + function transferAddressManagerOwnership() public broadcast { + console.log("Transferring AddressManager ownership to ProxyAdmin"); + AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); + address owner = addressManager.owner(); + address proxyAdmin = mustGetAddress("ProxyAdmin"); + if (owner != proxyAdmin) { + addressManager.transferOwnership(proxyAdmin); + console.log("AddressManager ownership transferred to %s", proxyAdmin); + } + + require(addressManager.owner() == proxyAdmin); + } + + /// @notice Deploy the L1DedicatedUSDCBridge + function deployL1DedicatedBridge() public broadcast returns (address addr_) { + console.log("Deploying L1DedicatedUSDCBridge implementation"); + + L1DedicatedUSDCBridge bridge = new L1DedicatedUSDCBridge{ salt: _implSalt() }(); + + save("L1DedicatedUSDCBridge", address(bridge)); + console.log("L1DedicatedUSDCBridge deployed at %s", address(bridge)); + + addr_ = address(bridge); + } + + /// @notice Deploy the L2DedicatedBridge + function deployL2DedicatedBridge() public broadcast returns (address addr_) { + console.log("Deploying L2DedicatedBridge implementation"); + + L2DedicatedBridge bridge = new L2DedicatedBridge{ salt: _implSalt() }(); + + save("L2DedicatedBridge", address(bridge)); + console.log("L2DedicatedBridge deployed at %s", address(bridge)); + + addr_ = address(bridge); + } + + //////////////////////////////////////////////////////////////// + // Initialize Functions // + //////////////////////////////////////////////////////////////// + + /// @notice Initialize the SuperchainConfig + function initializeSuperchainConfig() public broadcast { + address payable superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + address payable superchainConfig = mustGetAddress("SuperchainConfig"); + _upgradeAndCallViaSafe({ + _proxy: superchainConfigProxy, + _implementation: superchainConfig, + _innerCallData: abi.encodeCall(SuperchainConfig.initialize, (cfg.superchainConfigGuardian(), false)) + }); + + ChainAssertions.checkSuperchainConfig({ _contracts: _proxiesUnstrict(), _cfg: cfg, _isPaused: false }); + } + + /// @notice Initialize the L1DedicatedUSDCBridge + function initializeL1DedicatedBridge(address _otherBridge, address _l1Token, address _l2Token) public broadcast { + console.log("Upgrading and initializing L1DedicatedUSDCBridge proxy"); + ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); + address l1DedicatedBridgeProxy = mustGetAddress("L1DedicatedBridgeProxy"); + + uint256 proxyType = uint256(proxyAdmin.proxyType(l1DedicatedBridgeProxy)); + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) { + _callViaSafe({ + _safe: safe, + _target: address(proxyAdmin), + _data: abi.encodeCall(ProxyAdmin.setProxyType, (l1DedicatedBridgeProxy, ProxyAdmin.ProxyType.CHUGSPLASH)) + }); + } + require(uint256(proxyAdmin.proxyType(l1DedicatedBridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); + + _upgradeAndCallViaSafe({ + _proxy: payable(l1DedicatedBridgeProxy), + _implementation: mustGetAddress("L1DedicatedUSDCBridge"), + _innerCallData: abi.encodeCall( + L1DedicatedBridge.initialize, + ( + L1CrossDomainMessenger(cfg.l1CrossDomainMessengerProxy()), + SuperchainConfig(mustGetAddress("SuperchainConfigProxy")), + SystemConfig(cfg.systemConfigProxy()), + _otherBridge, + _l1Token, + _l2Token + ) + ) + }); + + string memory version = L1DedicatedUSDCBridge(payable(l1DedicatedBridgeProxy)).version(); + console.log("L1DedicatedUSDCBridge version: %s", version); + } + + /// @notice Initialize the L2DedicatedBridge + function initializeL2DedicatedBridge( + address _proxy, + address _implementation, + address _otherBridge, + address _l1Token, + address _l2Token + ) + public + broadcast + { + bytes memory data = abi.encodeCall(L2DedicatedBridge.initialize, (_otherBridge, _l1Token, _l2Token)); + Proxy(_proxy).upgradeToAndCall(_implementation, _data); + } +} From bb40d8d4331e68a7514f0ee3a446649fe66b8d3b Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Mon, 24 Jun 2024 18:19:51 +0200 Subject: [PATCH 13/15] simplify deploy contract --- packages/contracts-bedrock/scripts/Config.sol | 2 +- .../scripts/DeployDedicatedBridge.s.sol | 397 +----------------- 2 files changed, 6 insertions(+), 393 deletions(-) diff --git a/packages/contracts-bedrock/scripts/Config.sol b/packages/contracts-bedrock/scripts/Config.sol index 938919a22e8..e4cec30c340 100644 --- a/packages/contracts-bedrock/scripts/Config.sol +++ b/packages/contracts-bedrock/scripts/Config.sol @@ -94,7 +94,7 @@ library Config { /// @notice The CREATE2 salt to be used when deploying the implementations. function implSalt() internal view returns (string memory _env) { - _env = vm.envOr("IMPL_SALT", string("ethers phoenix sergio")); + _env = vm.envOr("IMPL_SALT", string("ethers phoenix avocado")); } /// @notice Returns the path that the state dump file should be written to or read from diff --git a/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol b/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol index 7fa922a88e8..2189a9c72d6 100644 --- a/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol +++ b/packages/contracts-bedrock/scripts/DeployDedicatedBridge.s.sol @@ -8,220 +8,29 @@ import { console2 as console } from "forge-std/console2.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; -import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; -import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; -import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; -import { Deployer } from "scripts/Deployer.sol"; +import { Deploy } from "scripts/Deploy.s.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; -import { AddressManager } from "src/legacy/AddressManager.sol"; -import { Proxy } from "src/universal/Proxy.sol"; import { DedicatedBridge } from "src/universal/DedicatedBridge.sol"; import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; import { L1DedicatedUSDCBridge } from "src/L1/L1DedicatedUSDCBridge.sol"; import { L2DedicatedBridge } from "src/L2/L2DedicatedBridge.sol"; import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; -import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; -import { SystemConfigInterop } from "src/L1/SystemConfigInterop.sol"; -import { ResourceMetering } from "src/L1/ResourceMetering.sol"; -import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol"; -import { Constants } from "src/libraries/Constants.sol"; -import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; -import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; -import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; -import { StorageSetter } from "src/universal/StorageSetter.sol"; -import { Predeploys } from "src/libraries/Predeploys.sol"; import { Proxy } from "src/universal/Proxy.sol"; -import { Chains } from "scripts/Chains.sol"; -import { Config } from "scripts/Config.sol"; -import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; import "src/dispute/lib/Types.sol"; -import { ChainAssertions } from "scripts/ChainAssertions.sol"; -import { Types } from "scripts/Types.sol"; -import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; -import { Process } from "scripts/libraries/Process.sol"; -/// @title Deploy +/// @title DeployDedicatedBridge /// @notice Script used to deploy a dedicated bridge. -contract DeployDedicatedBridge is Deployer { +contract DeployDedicatedBridge is Deploy { using stdJson for string; - //////////////////////////////////////////////////////////////// - // Modifiers // - //////////////////////////////////////////////////////////////// - - /// @notice Modifier that wraps a function in broadcasting. - modifier broadcast() { - vm.startBroadcast(msg.sender); - _; - vm.stopBroadcast(); - } - - /// @notice Modifier that will only allow a function to be called on devnet. - modifier onlyDevnet() { - uint256 chainid = block.chainid; - if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) { - _; - } - } - - /// @notice Modifier that will only allow a function to be called on a public - /// testnet or devnet. - modifier onlyTestnetOrDevnet() { - uint256 chainid = block.chainid; - if ( - chainid == Chains.Goerli || chainid == Chains.Sepolia || chainid == Chains.LocalDevnet - || chainid == Chains.GethDevnet - ) { - _; - } - } - - /// @notice Modifier that wraps a function with statediff recording. - /// The returned AccountAccess[] array is then written to - /// the `snapshots/state-diff/.json` output file. - modifier stateDiff() { - vm.startStateDiffRecording(); - _; - VmSafe.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff(); - console.log( - "Writing %d state diff account accesses to snapshots/state-diff/%s.json", - accesses.length, - vm.toString(block.chainid) - ); - string memory json = LibStateDiff.encodeAccountAccesses(accesses); - string memory statediffPath = - string.concat(vm.projectRoot(), "/snapshots/state-diff/", vm.toString(block.chainid), ".json"); - vm.writeJson({ json: json, path: statediffPath }); - } - - //////////////////////////////////////////////////////////////// - // Accessors // - //////////////////////////////////////////////////////////////// - - /// @notice The create2 salt used for deployment of the contract implementations. - /// Using this helps to reduce config across networks as the implementation - /// addresses will be the same across networks when deployed with create2. - function _implSalt() internal view returns (bytes32) { - return keccak256(bytes(Config.implSalt())); - } - - /// @notice Returns the proxy addresses. If a proxy is not found, it will have address(0). - function _proxies() internal view returns (Types.ContractSet memory proxies_) { - proxies_ = Types.ContractSet({ - L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"), - L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"), - L2OutputOracle: mustGetAddress("L2OutputOracleProxy"), - DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), - DelayedWETH: mustGetAddress("DelayedWETHProxy"), - AnchorStateRegistry: mustGetAddress("AnchorStateRegistryProxy"), - OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), - OptimismPortal: mustGetAddress("OptimismPortalProxy"), - OptimismPortal2: mustGetAddress("OptimismPortalProxy"), - SystemConfig: mustGetAddress("SystemConfigProxy"), - L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"), - ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"), - SuperchainConfig: mustGetAddress("SuperchainConfigProxy") - }); - } - - /// @notice Returns the proxy addresses, not reverting if any are unset. - function _proxiesUnstrict() internal view returns (Types.ContractSet memory proxies_) { - proxies_ = Types.ContractSet({ - L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"), - L1StandardBridge: getAddress("L1StandardBridgeProxy"), - L2OutputOracle: getAddress("L2OutputOracleProxy"), - DisputeGameFactory: getAddress("DisputeGameFactoryProxy"), - DelayedWETH: getAddress("DelayedWETHProxy"), - AnchorStateRegistry: getAddress("AnchorStateRegistryProxy"), - OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"), - OptimismPortal: getAddress("OptimismPortalProxy"), - OptimismPortal2: getAddress("OptimismPortalProxy"), - SystemConfig: getAddress("SystemConfigProxy"), - L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), - ProtocolVersions: getAddress("ProtocolVersionsProxy"), - SuperchainConfig: getAddress("SuperchainConfigProxy") - }); - } - - //////////////////////////////////////////////////////////////// - // State Changing Helper Functions // - //////////////////////////////////////////////////////////////// - - /// @notice Gets the address of the SafeProxyFactory and Safe singleton for use in deploying a new GnosisSafe. - function _getSafeFactory() internal returns (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_) { - if (getAddress("SafeProxyFactory") != address(0)) { - // The SafeProxyFactory is already saved, we can just use it. - safeProxyFactory_ = SafeProxyFactory(getAddress("SafeProxyFactory")); - safeSingleton_ = Safe(getAddress("SafeSingleton")); - return (safeProxyFactory_, safeSingleton_); - } - - // These are the standard create2 deployed contracts. First we'll check if they are deployed, - // if not we'll deploy new ones, though not at these addresses. - address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; - address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; - - safeProxyFactory.code.length == 0 - ? safeProxyFactory_ = new SafeProxyFactory() - : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory); - - safeSingleton.code.length == 0 ? safeSingleton_ = new Safe() : safeSingleton_ = Safe(payable(safeSingleton)); - - save("SafeProxyFactory", address(safeProxyFactory_)); - save("SafeSingleton", address(safeSingleton_)); - } - - /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data - function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { - // This is the signature format used the caller is also the signer. - bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); - - _safe.execTransaction({ - to: _target, - value: 0, - data: _data, - operation: SafeOps.Operation.Call, - safeTxGas: 0, - baseGas: 0, - gasPrice: 0, - gasToken: address(0), - refundReceiver: payable(address(0)), - signatures: signature - }); - } - - /// @notice Call from the Safe contract to the Proxy Admin's upgrade and call method - function _upgradeAndCallViaSafe(address _proxy, address _implementation, bytes memory _innerCallData) internal { - address proxyAdmin = mustGetAddress("ProxyAdmin"); - - bytes memory data = - abi.encodeCall(ProxyAdmin.upgradeAndCall, (payable(_proxy), _implementation, _innerCallData)); - - Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); - _callViaSafe({ _safe: safe, _target: proxyAdmin, _data: data }); - } - - /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner - function transferProxyAdminOwnership() public { - ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); - address owner = proxyAdmin.owner(); - address safe = mustGetAddress("SystemOwnerSafe"); - if (owner != safe) { - proxyAdmin.transferOwnership(safe); - console.log("ProxyAdmin ownership transferred to Safe at: %s", safe); - } - } //////////////////////////////////////////////////////////////// // High Level Deployment Functions // @@ -252,130 +61,6 @@ contract DeployDedicatedBridge is Deployer { console.log("Done!"); } - // /// @notice Deploy the L1DedicatedBridge - // function deployAndInitializeL2DedicatedBridge( - // address _proxy, - // address _otherBridge, - // address _l1Token, - // address _l2Token - // ) - // public - // returns (address addr_) - // { - // addr_ = deployL2DedicatedBridge(); - // initializeL2DedicatedBridge(_proxy, _otherBridge, _l1Token, _l2Token); - - // addr_; - // } - - //////////////////////////////////////////////////////////////// - // Non-Proxied Deployment Functions // - //////////////////////////////////////////////////////////////// - - /// @notice Deploy the Safe - function deploySafe(string memory _name) public returns (address addr_) { - address[] memory owners = new address[](0); - addr_ = deploySafe(_name, owners, 1, true); - } - - /// @notice Deploy a new Safe contract. If the keepDeployer option is used to enable further setup actions, then - /// the removeDeployerFromSafe() function should be called on that safe after setup is complete. - /// Note this function does not have the broadcast modifier. - /// @param _name The name of the Safe to deploy. - /// @param _owners The owners of the Safe. - /// @param _threshold The threshold of the Safe. - /// @param _keepDeployer Wether or not the deployer address will be added as an owner of the Safe. - function deploySafe( - string memory _name, - address[] memory _owners, - uint256 _threshold, - bool _keepDeployer - ) - public - broadcast - returns (address addr_) - { - bytes32 salt = keccak256(abi.encode(_name, _implSalt())); - console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); - (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); - - address[] memory expandedOwners = new address[](_owners.length + 1); - if (_keepDeployer) { - // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it - // easier to call removeOwner later. - expandedOwners[0] = msg.sender; - for (uint256 i = 0; i < _owners.length; i++) { - expandedOwners[i + 1] = _owners[i]; - } - _owners = expandedOwners; - } - - bytes memory initData = abi.encodeCall( - Safe.setup, (_owners, _threshold, address(0), hex"", address(0), address(0), 0, payable(address(0))) - ); - addr_ = address(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, uint256(salt))); - - save(_name, addr_); - console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, addr_); - } - - /// @notice If the keepDeployer option was used with deploySafe(), this function can be used to remove the deployer. - /// Note this function does not have the broadcast modifier. - function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public { - Safe safe = Safe(mustGetAddress(_name)); - - // The sentinel address is used to mark the start and end of the linked list of owners in the Safe. - address sentinelOwners = address(0x1); - - // Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner - // will be sentinelOwners. - _callViaSafe({ - _safe: safe, - _target: address(safe), - _data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold)) - }); - console.log("Removed deployer owner from ", _name); - } - - /// @notice Deploy the AddressManager - function deployAddressManager() public broadcast returns (address addr_) { - console.log("Deploying AddressManager"); - AddressManager manager = new AddressManager(); - require(manager.owner() == msg.sender); - - save("AddressManager", address(manager)); - console.log("AddressManager deployed at %s", address(manager)); - addr_ = address(manager); - } - - /// @notice Deploy the ProxyAdmin - function deployProxyAdmin() public broadcast returns (address addr_) { - console.log("Deploying ProxyAdmin"); - ProxyAdmin admin = new ProxyAdmin({ _owner: msg.sender }); - require(admin.owner() == msg.sender); - - AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); - if (admin.addressManager() != addressManager) { - admin.setAddressManager(addressManager); - } - - require(admin.addressManager() == addressManager); - - save("ProxyAdmin", address(admin)); - console.log("ProxyAdmin deployed at %s", address(admin)); - addr_ = address(admin); - } - - /// @notice Deploy the StorageSetter contract, used for upgrades. - function deployStorageSetter() public broadcast returns (address addr_) { - console.log("Deploying StorageSetter"); - StorageSetter setter = new StorageSetter{ salt: _implSalt() }(); - console.log("StorageSetter deployed at: %s", address(setter)); - string memory version = setter.version(); - console.log("StorageSetter version: %s", version); - addr_ = address(setter); - } - //////////////////////////////////////////////////////////////// // Proxy Deployment Functions // //////////////////////////////////////////////////////////////// @@ -399,65 +84,6 @@ contract DeployDedicatedBridge is Deployer { addr_ = deployERC1967ProxyWithOwner("L2DedicatedBridgeProxy", msg.sender); } - /// @notice Deploys an ERC1967Proxy contract with the ProxyAdmin as the owner. - /// @param _name The name of the proxy contract to be deployed. - /// @return addr_ The address of the deployed proxy contract. - function deployERC1967Proxy(string memory _name) public broadcast returns (address addr_) { - addr_ = deployERC1967ProxyWithOwner(_name, mustGetAddress("ProxyAdmin")); - } - - /// @notice Deploys an ERC1967Proxy contract with a specified owner. - /// @param _name The name of the proxy contract to be deployed. - /// @param _proxyOwner The address of the owner of the proxy contract. - /// @return addr_ The address of the deployed proxy contract. - function deployERC1967ProxyWithOwner( - string memory _name, - address _proxyOwner - ) - public - broadcast - returns (address addr_) - { - console.log(string.concat("Deploying ERC1967 proxy for ", _name)); - Proxy proxy = new Proxy({ _admin: _proxyOwner }); - - require(EIP1967Helper.getAdmin(address(proxy)) == _proxyOwner); - - save(_name, address(proxy)); - console.log(" at %s", address(proxy)); - addr_ = address(proxy); - } - - //////////////////////////////////////////////////////////////// - // Implementation Deployment Functions // - //////////////////////////////////////////////////////////////// - - /// @notice Deploy the SuperchainConfig contract - function deploySuperchainConfig() public broadcast { - SuperchainConfig superchainConfig = new SuperchainConfig{ salt: _implSalt() }(); - - require(superchainConfig.guardian() == address(0)); - bytes32 initialized = vm.load(address(superchainConfig), bytes32(0)); - require(initialized != 0); - - save("SuperchainConfig", address(superchainConfig)); - console.log("SuperchainConfig deployed at %s", address(superchainConfig)); - } - - /// @notice Transfer ownership of the address manager to the ProxyAdmin - function transferAddressManagerOwnership() public broadcast { - console.log("Transferring AddressManager ownership to ProxyAdmin"); - AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); - address owner = addressManager.owner(); - address proxyAdmin = mustGetAddress("ProxyAdmin"); - if (owner != proxyAdmin) { - addressManager.transferOwnership(proxyAdmin); - console.log("AddressManager ownership transferred to %s", proxyAdmin); - } - - require(addressManager.owner() == proxyAdmin); - } - /// @notice Deploy the L1DedicatedUSDCBridge function deployL1DedicatedBridge() public broadcast returns (address addr_) { console.log("Deploying L1DedicatedUSDCBridge implementation"); @@ -486,19 +112,6 @@ contract DeployDedicatedBridge is Deployer { // Initialize Functions // //////////////////////////////////////////////////////////////// - /// @notice Initialize the SuperchainConfig - function initializeSuperchainConfig() public broadcast { - address payable superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - address payable superchainConfig = mustGetAddress("SuperchainConfig"); - _upgradeAndCallViaSafe({ - _proxy: superchainConfigProxy, - _implementation: superchainConfig, - _innerCallData: abi.encodeCall(SuperchainConfig.initialize, (cfg.superchainConfigGuardian(), false)) - }); - - ChainAssertions.checkSuperchainConfig({ _contracts: _proxiesUnstrict(), _cfg: cfg, _isPaused: false }); - } - /// @notice Initialize the L1DedicatedUSDCBridge function initializeL1DedicatedBridge(address _otherBridge, address _l1Token, address _l2Token) public broadcast { console.log("Upgrading and initializing L1DedicatedUSDCBridge proxy"); @@ -547,7 +160,7 @@ contract DeployDedicatedBridge is Deployer { public broadcast { - bytes memory data = abi.encodeCall(L2DedicatedBridge.initialize, (_otherBridge, _l1Token, _l2Token)); - Proxy(_proxy).upgradeToAndCall(_implementation, _data); + bytes memory _data = abi.encodeCall(L2DedicatedBridge.initialize, (_otherBridge, _l1Token, _l2Token)); + Proxy(payable(_proxy)).upgradeToAndCall(_implementation, _data); } } From 5d2f069ddcb454ef0f5b5934030d6ce3d64a924c Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 25 Jun 2024 10:31:53 +0200 Subject: [PATCH 14/15] cleanup --- packages/contracts-bedrock/scripts/ChainAssertions.sol | 1 - packages/contracts-bedrock/scripts/Config.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/scripts/ChainAssertions.sol b/packages/contracts-bedrock/scripts/ChainAssertions.sol index 35a2fd171de..a99c14e9514 100644 --- a/packages/contracts-bedrock/scripts/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/ChainAssertions.sol @@ -8,7 +8,6 @@ import { Deployer } from "scripts/Deployer.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { Constants } from "src/libraries/Constants.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; -import { L1DedicatedBridge } from "src/L1/L1DedicatedBridge.sol"; import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; diff --git a/packages/contracts-bedrock/scripts/Config.sol b/packages/contracts-bedrock/scripts/Config.sol index e4cec30c340..0bf567b3dd1 100644 --- a/packages/contracts-bedrock/scripts/Config.sol +++ b/packages/contracts-bedrock/scripts/Config.sol @@ -94,7 +94,7 @@ library Config { /// @notice The CREATE2 salt to be used when deploying the implementations. function implSalt() internal view returns (string memory _env) { - _env = vm.envOr("IMPL_SALT", string("ethers phoenix avocado")); + _env = vm.envOr("IMPL_SALT", string("ethers phoenix")); } /// @notice Returns the path that the state dump file should be written to or read from From 44ced9f5fed416cfb46a3c95859e55bb4d091f2e Mon Sep 17 00:00:00 2001 From: Alessandro Ricottone Date: Tue, 25 Jun 2024 12:34:07 +0200 Subject: [PATCH 15/15] deployment script --- .../contracts-bedrock/scripts/usdc_deploy.sh | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 packages/contracts-bedrock/scripts/usdc_deploy.sh diff --git a/packages/contracts-bedrock/scripts/usdc_deploy.sh b/packages/contracts-bedrock/scripts/usdc_deploy.sh new file mode 100755 index 00000000000..d33f1040251 --- /dev/null +++ b/packages/contracts-bedrock/scripts/usdc_deploy.sh @@ -0,0 +1,46 @@ +DEPLOY_CONFIG_PATH="deploy-config/usdc-sepolia-devnet.json" +USDC_L1_ADDRESS=0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 +USDC_L2_ADDRESS=0x85977F663949E5AC21334F2219E2B4048EF74A80 + +# source .env for PRIVATE_KEY +source ../.env + +export IMPL_SALT="avocado magnetico salato" + +L1_VERIFIER_URL=https://eth-sepolia.blockscout.com/api\? +L2_VERIFIER_URL=https://sepolia-blockscout.lisk.com/api\? + +# L1_RPC_URL=wss://ethereum-sepolia-rpc.publicnode.com +# L2_RPC_URL=https://rpc.sepolia-api.lisk.com + +L1_RPC_URL=http://localhost:8545 +L2_RPC_URL=http://localhost:8546 + +# outputs L2DedicatedBridgeProxy +forge script -vvv DeployDedicatedBridge.s.sol:DeployDedicatedBridge --sig 'deployL2DedicatedBridgeProxy()' --rpc-url "$L2_RPC_URL" --broadcast --private-key $PRIVATE_KEY +L2DedicatedBridgeProxy=$(cat ../deployments/4202-deploy.json | grep -Eo '"L2DedicatedBridgeProxy": "(\d*?,|.*?[^\\])"' | awk -F'"' '{print $4}' ) + +# outputs USDC_L1_BRIDGE_PROXY and USDC_L1_BRIDGE_DEPLOYMENT +forge script -vvv DeployDedicatedBridge.s.sol:DeployDedicatedBridge $L2DedicatedBridgeProxy $USDC_L1_ADDRESS $USDC_L2_ADDRESS --sig 'runL1DedicatedBridgeDeployment(address,address,address)' --rpc-url "$L1_RPC_URL" --broadcast --private-key $PRIVATE_KEY +L1DedicatedBridgeProxy=$(cat ../deployments/11155111-deploy.json | grep -Eo '"L1DedicatedBridgeProxy": "(\d*?,|.*?[^\\])"' | awk -F'"' '{print $4}' ) +L1DedicatedUSDCBridge=$(cat ../deployments/11155111-deploy.json | grep -Eo '"L1DedicatedUSDCBridge": "(\d*?,|.*?[^\\])"' | awk -F'"' '{print $4}' ) + + +# outputs USDC_L2_BRIDGE_DEPLOYMENT +forge script -vvv DeployDedicatedBridge.s.sol:DeployDedicatedBridge --sig 'deployL2DedicatedBridge()' --rpc-url "$L2_RPC_URL" --broadcast --private-key $PRIVATE_KEY +L2DedicatedBridge=$(cat ../deployments/4202-deploy.json | grep -Eo '"L2DedicatedBridge": "(\d*?,|.*?[^\\])"' | awk -F'"' '{print $4}' ) + +forge script -vvv DeployDedicatedBridge.s.sol:DeployDedicatedBridge $L2DedicatedBridgeProxy $L2DedicatedBridge $L1DedicatedBridgeProxy $USDC_L1_ADDRESS $USDC_L2_ADDRESS --sig 'initializeL2DedicatedBridge(address,address,address,address,address)' --rpc-url "$L2_RPC_URL" --broadcast --private-key $PRIVATE_KEY + + +# forge verify-contract $USDC_L1_BRIDGE_DEPLOYMENT src/L1/L1DedicatedUSDCBridge.sol:L1DedicatedUSDCBridge --compiler-version 0.8.15 --rpc-url "$L1_RPC_URL" --verifier blockscout --verifier-url $L1_VERIFIER_URL +# forge verify-contract $USDC_L2_BRIDGE_DEPLOYMENT src/L2/L2DedicatedBridge.sol:L2DedicatedBridge --compiler-version 0.8.15 --rpc-url "$L2_RPC_URL" --verifier blockscout --verifier-url $L2_VERIFIER_URL + + + + +echo "All done!" +echo "L1DedicatedBridgeProxy", $L1DedicatedBridgeProxy +echo "L1DedicatedUSDCBridge", $L1DedicatedUSDCBridge +echo "L2DedicatedBridgeProxy", $L2DedicatedBridgeProxy +echo "L2DedicatedBridge", $L2DedicatedBridge