StandardBridge.sol 21.7 KB
Newer Older
1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.15;
3

4 5 6 7
// Contracts
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";

// Libraries
8
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
9
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
10
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
11
import { SafeCall } from "src/libraries/SafeCall.sol";
12 13 14

// Interfaces
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
15
import { IOptimismMintableERC20 } from "interfaces/universal/IOptimismMintableERC20.sol";
16
import { ILegacyMintableERC20 } from "interfaces/legacy/ILegacyMintableERC20.sol";
17
import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol";
18

19 20 21 22 23
/// @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.
24
abstract contract StandardBridge is Initializable {
25 26
    using SafeERC20 for IERC20;

27
    /// @notice The L2 gas limit set when eth is depoisited using the receive() function.
28 29
    uint32 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 200_000;

30
    /// @custom:legacy
31
    /// @custom:spacer messenger
32
    /// @notice Spacer for backwards compatibility.
33
    bytes30 private spacer_0_2_30;
34

35 36 37
    /// @custom:legacy
    /// @custom:spacer l2TokenBridge
    /// @notice Spacer for backwards compatibility.
38
    address private spacer_1_0_20;
39

40
    /// @notice Mapping that stores deposits for a given pair of local and remote tokens.
41 42
    mapping(address => mapping(address => uint256)) public deposits;

43 44 45
    /// @notice Messenger contract on this domain.
    /// @custom:network-specific
    ICrossDomainMessenger public messenger;
Mark Tyneway's avatar
Mark Tyneway committed
46

47 48 49
    /// @notice Corresponding bridge on the other domain.
    /// @custom:network-specific
    StandardBridge public otherBridge;
Mark Tyneway's avatar
Mark Tyneway committed
50

51
    /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
52 53 54
    ///         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;
55

56 57 58 59 60
    /// @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.
Maurelian's avatar
Maurelian committed
61
    event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData);
62

63 64 65 66 67
    /// @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.
Maurelian's avatar
Maurelian committed
68
    event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData);
69

70 71 72 73 74 75 76
    /// @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.
77
    event ERC20BridgeInitiated(
78 79 80 81 82 83
        address indexed localToken,
        address indexed remoteToken,
        address indexed from,
        address to,
        uint256 amount,
        bytes extraData
84 85
    );

86 87 88 89 90 91 92
    /// @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.
93
    event ERC20BridgeFinalized(
94 95 96 97 98 99
        address indexed localToken,
        address indexed remoteToken,
        address indexed from,
        address to,
        uint256 amount,
        bytes extraData
100 101
    );

102 103 104
    /// @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.
105
    modifier onlyEOA() {
Maurelian's avatar
Maurelian committed
106
        require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA");
107 108 109
        _;
    }

110
    /// @notice Ensures that the caller is a cross-chain message from the other bridge.
111 112
    modifier onlyOtherBridge() {
        require(
113
            msg.sender == address(messenger) && messenger.xDomainMessageSender() == address(otherBridge),
114
            "StandardBridge: function can only be called from the other bridge"
115 116 117 118
        );
        _;
    }

119 120 121 122 123 124 125 126 127 128 129 130 131 132
    /// @notice Initializer.
    /// @param _messenger   Contract for CrossDomainMessenger on this network.
    /// @param _otherBridge Contract for the other StandardBridge contract.
    function __StandardBridge_init(
        ICrossDomainMessenger _messenger,
        StandardBridge _otherBridge
    )
        internal
        onlyInitializing
    {
        messenger = _messenger;
        otherBridge = _otherBridge;
    }

133 134
    /// @notice Allows EOAs to bridge ETH by sending directly to the bridge.
    ///         Must be implemented by contracts that inherit.
135
    receive() external payable virtual;
136

137
    /// @notice Getter for messenger contract.
Mark Tyneway's avatar
Mark Tyneway committed
138 139 140
    ///         Public getter is legacy and will be removed in the future. Use `messenger` instead.
    /// @return Contract of the messenger on this domain.
    /// @custom:legacy
141
    function MESSENGER() external view returns (ICrossDomainMessenger) {
142
        return messenger;
143 144
    }

Mark Tyneway's avatar
Mark Tyneway committed
145 146 147 148
    /// @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
149 150
    function OTHER_BRIDGE() external view returns (StandardBridge) {
        return otherBridge;
151 152
    }

153 154 155 156 157 158 159 160
    /// @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;
    }

161 162 163 164 165
    /// @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.
166 167
    function bridgeETH(uint32 _minGasLimit, bytes calldata _extraData) public payable onlyEOA {
        _initiateBridgeETH(msg.sender, msg.sender, msg.value, _minGasLimit, _extraData);
168 169
    }

170 171 172 173 174 175 176 177 178 179 180 181
    /// @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.
Maurelian's avatar
Maurelian committed
182
    function bridgeETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) public payable {
183
        _initiateBridgeETH(msg.sender, _to, msg.value, _minGasLimit, _extraData);
184 185
    }

186
    /// @notice Sends ERC20 tokens to the sender's address on the other chain.
187 188 189 190 191 192 193
    /// @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.
194 195 196 197 198
    function bridgeERC20(
        address _localToken,
        address _remoteToken,
        uint256 _amount,
        uint32 _minGasLimit,
199
        bytes calldata _extraData
Maurelian's avatar
Maurelian committed
200 201 202 203 204 205
    )
        public
        virtual
        onlyEOA
    {
        _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, msg.sender, _amount, _minGasLimit, _extraData);
206 207
    }

208
    /// @notice Sends ERC20 tokens to a receiver's address on the other chain.
209 210 211 212 213 214 215 216
    /// @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.
217 218 219 220 221 222
    function bridgeERC20To(
        address _localToken,
        address _remoteToken,
        address _to,
        uint256 _amount,
        uint32 _minGasLimit,
223
        bytes calldata _extraData
Maurelian's avatar
Maurelian committed
224 225 226 227 228
    )
        public
        virtual
    {
        _initiateBridgeERC20(_localToken, _remoteToken, msg.sender, _to, _amount, _minGasLimit, _extraData);
229 230
    }

231 232 233 234 235 236 237 238
    /// @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.
239 240 241 242
    function finalizeBridgeETH(
        address _from,
        address _to,
        uint256 _amount,
243
        bytes calldata _extraData
Maurelian's avatar
Maurelian committed
244 245 246 247 248
    )
        public
        payable
        onlyOtherBridge
    {
249
        require(paused() == false, "StandardBridge: paused");
250 251
        require(msg.value == _amount, "StandardBridge: amount sent does not match amount required");
        require(_to != address(this), "StandardBridge: cannot send to self");
252
        require(_to != address(messenger), "StandardBridge: cannot send to messenger");
253

254 255
        // 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.
256
        _emitETHBridgeFinalized(_from, _to, _amount, _extraData);
257

258
        bool success = SafeCall.call(_to, gasleft(), _amount, hex"");
259
        require(success, "StandardBridge: ETH transfer failed");
260 261
    }

262 263 264 265 266 267 268 269 270 271
    /// @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.
272 273 274 275 276 277
    function finalizeBridgeERC20(
        address _localToken,
        address _remoteToken,
        address _from,
        address _to,
        uint256 _amount,
278
        bytes calldata _extraData
Maurelian's avatar
Maurelian committed
279 280 281 282
    )
        public
        onlyOtherBridge
    {
283
        require(paused() == false, "StandardBridge: paused");
284 285 286
        if (_isOptimismMintableERC20(_localToken)) {
            require(
                _isCorrectTokenPair(_localToken, _remoteToken),
287
                "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token"
288 289
            );

290
            IOptimismMintableERC20(_localToken).mint(_to, _amount);
291 292 293 294
        } else {
            deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] - _amount;
            IERC20(_localToken).safeTransfer(_to, _amount);
        }
295

296 297
        // 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.
298
        _emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
299 300
    }

301 302 303 304 305 306 307 308
    /// @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.
309 310 311 312 313
    function _initiateBridgeETH(
        address _from,
        address _to,
        uint256 _amount,
        uint32 _minGasLimit,
314
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
315 316 317 318
    )
        internal
    {
        require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value");
319

320 321
        // 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.
322
        _emitETHBridgeInitiated(_from, _to, _amount, _extraData);
323

324 325
        messenger.sendMessage{ value: _amount }({
            _target: address(otherBridge),
Mark Tyneway's avatar
Mark Tyneway committed
326 327 328
            _message: abi.encodeWithSelector(this.finalizeBridgeETH.selector, _from, _to, _amount, _extraData),
            _minGasLimit: _minGasLimit
        });
329 330
    }

331 332 333 334 335 336 337 338 339
    /// @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.
340 341 342 343 344 345 346
    function _initiateBridgeERC20(
        address _localToken,
        address _remoteToken,
        address _from,
        address _to,
        uint256 _amount,
        uint32 _minGasLimit,
347
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
348 349 350
    )
        internal
    {
351 352
        require(msg.value == 0, "StandardBridge: cannot send value");

353 354 355
        if (_isOptimismMintableERC20(_localToken)) {
            require(
                _isCorrectTokenPair(_localToken, _remoteToken),
356
                "StandardBridge: wrong remote token for Optimism Mintable ERC20 local token"
357 358
            );

359
            IOptimismMintableERC20(_localToken).burn(_from, _amount);
360 361 362 363 364
        } else {
            IERC20(_localToken).safeTransferFrom(_from, address(this), _amount);
            deposits[_localToken][_remoteToken] = deposits[_localToken][_remoteToken] + _amount;
        }

365 366
        // 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.
367
        _emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
368

369 370
        messenger.sendMessage({
            _target: address(otherBridge),
Mark Tyneway's avatar
Mark Tyneway committed
371
            _message: abi.encodeWithSelector(
372
                this.finalizeBridgeERC20.selector,
373 374 375
                // 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.
376 377 378 379 380
                _remoteToken,
                _localToken,
                _from,
                _to,
                _amount,
381
                _extraData
382
            ),
Mark Tyneway's avatar
Mark Tyneway committed
383 384
            _minGasLimit: _minGasLimit
        });
385 386
    }

387 388 389 390
    /// @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.
391
    function _isOptimismMintableERC20(address _token) internal view returns (bool) {
Maurelian's avatar
Maurelian committed
392 393
        return ERC165Checker.supportsInterface(_token, type(ILegacyMintableERC20).interfaceId)
            || ERC165Checker.supportsInterface(_token, type(IOptimismMintableERC20).interfaceId);
394 395
    }

396 397 398 399 400 401
    /// @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.
Maurelian's avatar
Maurelian committed
402 403
    function _isCorrectTokenPair(address _mintableToken, address _otherToken) internal view returns (bool) {
        if (ERC165Checker.supportsInterface(_mintableToken, type(ILegacyMintableERC20).interfaceId)) {
404 405 406 407
            return _otherToken == ILegacyMintableERC20(_mintableToken).l1Token();
        } else {
            return _otherToken == IOptimismMintableERC20(_mintableToken).remoteToken();
        }
408
    }
409

410 411 412 413 414 415
    /// @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.
416 417 418 419 420
    function _emitETHBridgeInitiated(
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
421 422 423 424
    )
        internal
        virtual
    {
425 426 427
        emit ETHBridgeInitiated(_from, _to, _amount, _extraData);
    }

428 429 430 431 432 433
    /// @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.
434 435 436 437 438
    function _emitETHBridgeFinalized(
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
439 440 441 442
    )
        internal
        virtual
    {
443 444 445
        emit ETHBridgeFinalized(_from, _to, _amount, _extraData);
    }

446 447 448 449 450 451 452 453
    /// @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.
454 455 456 457 458 459 460
    function _emitERC20BridgeInitiated(
        address _localToken,
        address _remoteToken,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
461 462 463 464
    )
        internal
        virtual
    {
465 466 467
        emit ERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
    }

468 469 470 471 472 473 474 475
    /// @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.
476 477 478 479 480 481 482
    function _emitERC20BridgeFinalized(
        address _localToken,
        address _remoteToken,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _extraData
Maurelian's avatar
Maurelian committed
483 484 485 486
    )
        internal
        virtual
    {
487
        emit ERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
488
    }
489
}