diff --git a/packages/contracts-bedrock/contracts/L1/L1StandardBridgeWithBlackList.sol b/packages/contracts-bedrock/contracts/L1/L1StandardBridgeWithBlackList.sol new file mode 100644 index 000000000000..392588506b44 --- /dev/null +++ b/packages/contracts-bedrock/contracts/L1/L1StandardBridgeWithBlackList.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Predeploys } from "../libraries/Predeploys.sol"; +import { StandardBridge } from "../universal/StandardBridgeWithBlackList.sol"; +import { Semver } from "../universal/Semver.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, Semver { + /** + * @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. + * @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 + ); + + /** + * @custom:semver 1.1.0 + * + * @param _messenger Address of the L1CrossDomainMessenger. + * @param _guardian Address that has the ability to add and remove black accounts. + */ + constructor(address payable _messenger, address _guardian) + Semver(1, 1, 0) + StandardBridge(_messenger, payable(Predeploys.L2_STANDARD_BRIDGE), _guardian) + {} + + /** + * @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("")); + } + + /** + * @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 + * @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 onlyEOA { + _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 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. + * @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); + } + + /** + * @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(OTHER_BRIDGE); + } + + /** + * @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. + * @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); + } + + /** + * @notice Emits the legacy ETHDepositInitiated 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 ETHDepositInitiated(_from, _to, _amount, _extraData); + super._emitETHBridgeInitiated(_from, _to, _amount, _extraData); + } + + /** + * @notice Emits the legacy ETHWithdrawalFinalized 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 ETHWithdrawalFinalized(_from, _to, _amount, _extraData); + super._emitETHBridgeFinalized(_from, _to, _amount, _extraData); + } + + /** + * @notice Emits the legacy ERC20DepositInitiated 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 ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } + + /** + * @notice Emits the legacy ERC20WithdrawalFinalized 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 ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData); + } +} diff --git a/packages/contracts-bedrock/contracts/universal/StandardBridgeWithBlackList.sol b/packages/contracts-bedrock/contracts/universal/StandardBridgeWithBlackList.sol new file mode 100644 index 000000000000..10da4b8d0bd1 --- /dev/null +++ b/packages/contracts-bedrock/contracts/universal/StandardBridgeWithBlackList.sol @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: MIT +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 "../libraries/SafeCall.sol"; +import { IOptimismMintableERC20, ILegacyMintableERC20 } from "./IOptimismMintableERC20.sol"; +import { CrossDomainMessenger } from "./CrossDomainMessenger.sol"; +import { OptimismMintableERC20 } from "./OptimismMintableERC20.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 { + 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 Messenger contract on this domain. + */ + CrossDomainMessenger public immutable MESSENGER; + + /** + * @notice Corresponding bridge on the other domain. + */ + StandardBridge public immutable OTHER_BRIDGE; + + /** + * @custom:legacy + * @custom:spacer messenger + * @notice Spacer for backwards compatibility. + */ + address private spacer_0_0_20; + + /** + * @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; + + /** + * @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + * A gap size of 47 was chosen here, so that the first slot used in a child contract + * would be a multiple of 50. + */ + uint256[47] private __gap; + + /** + * @notice Address that has the ability to add and remove black accounts. + */ + address public immutable GUARDIAN; + + /** + * @notice A array of black accounts which can't withdraw + */ + address[] private _blackList; + + /** + * @notice A map of black accounts + */ + mapping(address => uint256) private _blackListMap; + + /** + * @notice Emitted when the black account is added. + * + * @param blackAccount Address + */ + event blackAccountAdded(address blackAccount); + + /** + * @notice Emitted when the black account is removed. + * + * @param blackAccount Address + */ + event blackAccountRemoved(address blackAccount); + + /** + * @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. + * @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(OTHER_BRIDGE), + "StandardBridge: function can only be called from the other bridge" + ); + _; + } + + /** + * @param _messenger Address of CrossDomainMessenger on this network. + * @param _otherBridge Address of the other StandardBridge contract. + * @param _guardian Address that has the ability to add and remove black accounts. + */ + constructor(address payable _messenger, address payable _otherBridge, address _guardian) { + MESSENGER = CrossDomainMessenger(_messenger); + OTHER_BRIDGE = StandardBridge(_otherBridge); + GUARDIAN = _guardian; + } + + /** + * @notice Allows EOAs to bridge ETH by sending directly to the bridge. + * Must be implemented by contracts that inherit. + */ + receive() external payable virtual; + + /** + * @custom:legacy + * @notice Legacy getter for messenger contract. + * + * @return Messenger contract on this domain. + */ + function messenger() external view returns (CrossDomainMessenger) { + return MESSENGER; + } + + /** + * @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. Note that if the + * ERC20 token on the other chain does not recognize the local token as the correct + * pair token, the ERC20 bridge will fail and the tokens will be returned to sender on + * this 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. Note that if the + * ERC20 token on the other chain does not recognize the local token as the correct + * pair token, the ERC20 bridge will fail and the tokens will be returned to sender on + * this 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 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(!isBlackAccount(_from), "black account"); + 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. + * @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(!isBlackAccount(_from), "black account"); + 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( + 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 }( + address(OTHER_BRIDGE), + abi.encodeWithSelector( + this.finalizeBridgeETH.selector, + _from, + _to, + _amount, + _extraData + ), + _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. + * @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 { + 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. + _emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData); + + MESSENGER.sendMessage( + address(OTHER_BRIDGE), + 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 + ); + } + + /** + * @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. + * @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); + } + + /** + * @notice Add black account + * + * @param blackAccount address to add blacklist + */ + function addBlackAccount(address blackAccount) external { + require(msg.sender == GUARDIAN, "only guardian can add black account"); + require(_blackListMap[blackAccount] == 0, "The black account already exist!"); + _blackList.push(blackAccount); + _blackListMap[blackAccount] = _blackList.length; + emit blackAccountAdded(blackAccount); + } + + /** + * @notice remove black account + * + * @param blackAccount address to remove from blacklist + */ + function removeBlackAccount(address blackAccount) external { + require(msg.sender == GUARDIAN, "only guardian can remove black account"); + uint256 position = _blackListMap[blackAccount]; + if (position > 0) { + uint256 indexOf = position - 1; + if (_blackList.length > 1 && indexOf != _blackList.length - 1) { + address lastAddress = _blackList[_blackList.length - 1]; + _blackList[indexOf] = lastAddress; + _blackListMap[lastAddress] = indexOf + 1; + } + _blackList.pop(); + delete _blackListMap[blackAccount]; + emit blackAccountRemoved(blackAccount); + } + } + + /** + * @notice get black account list + * + * @return black account list + */ + function getBlackListList() external view returns (address[] memory) { + return _blackList; + } + + /** + * @notice get black account list + * + * @return Whether or not the address is black account + */ + function isBlackAccount(address blackAccount) public view returns (bool) { + return _blackListMap[blackAccount] != 0; + } +}