github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { ERC721Bridge } from "src/universal/ERC721Bridge.sol";
     5  import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
     6  import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
     7  import { IOptimismMintableERC721 } from "src/universal/IOptimismMintableERC721.sol";
     8  import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
     9  import { StandardBridge } from "src/universal/StandardBridge.sol";
    10  import { ISemver } from "src/universal/ISemver.sol";
    11  import { Constants } from "src/libraries/Constants.sol";
    12  import { Predeploys } from "src/libraries/Predeploys.sol";
    13  
    14  /// @title L2ERC721Bridge
    15  /// @notice The L2 ERC721 bridge is a contract which works together with the L1 ERC721 bridge to
    16  ///         make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract
    17  ///         acts as a minter for new tokens when it hears about deposits into the L1 ERC721 bridge.
    18  ///         This contract also acts as a burner for tokens being withdrawn.
    19  ///         **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This
    20  ///         bridge ONLY supports ERC721s originally deployed on Ethereum. Users will need to
    21  ///         wait for the one-week challenge period to elapse before their Optimism-native NFT
    22  ///         can be refunded on L2.
    23  contract L2ERC721Bridge is ERC721Bridge, ISemver {
    24      /// @custom:semver 1.7.0
    25      string public constant version = "1.7.0";
    26  
    27      /// @notice Constructs the L2ERC721Bridge contract.
    28      constructor() ERC721Bridge() {
    29          initialize({ _l1ERC721Bridge: payable(address(0)) });
    30      }
    31  
    32      /// @notice Initializes the contract.
    33      /// @param _l1ERC721Bridge Address of the ERC721 bridge contract on the other network.
    34      function initialize(address payable _l1ERC721Bridge) public initializer {
    35          __ERC721Bridge_init({
    36              _messenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER),
    37              _otherBridge: StandardBridge(_l1ERC721Bridge)
    38          });
    39      }
    40  
    41      /// @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the
    42      ///         recipient on this domain.
    43      /// @param _localToken  Address of the ERC721 token on this domain.
    44      /// @param _remoteToken Address of the ERC721 token on the other domain.
    45      /// @param _from        Address that triggered the bridge on the other domain.
    46      /// @param _to          Address to receive the token on this domain.
    47      /// @param _tokenId     ID of the token being deposited.
    48      /// @param _extraData   Optional data to forward to L1.
    49      ///                     Data supplied here will not be used to execute any code on L1 and is
    50      ///                     only emitted as extra data for the convenience of off-chain tooling.
    51      function finalizeBridgeERC721(
    52          address _localToken,
    53          address _remoteToken,
    54          address _from,
    55          address _to,
    56          uint256 _tokenId,
    57          bytes calldata _extraData
    58      )
    59          external
    60          onlyOtherBridge
    61      {
    62          require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self");
    63  
    64          // Note that supportsInterface makes a callback to the _localToken address which is user
    65          // provided.
    66          require(
    67              ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId),
    68              "L2ERC721Bridge: local token interface is not compliant"
    69          );
    70  
    71          require(
    72              _remoteToken == IOptimismMintableERC721(_localToken).remoteToken(),
    73              "L2ERC721Bridge: wrong remote token for Optimism Mintable ERC721 local token"
    74          );
    75  
    76          // When a deposit is finalized, we give the NFT with the same tokenId to the account
    77          // on L2. Note that safeMint makes a callback to the _to address which is user provided.
    78          IOptimismMintableERC721(_localToken).safeMint(_to, _tokenId);
    79  
    80          // slither-disable-next-line reentrancy-events
    81          emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
    82      }
    83  
    84      /// @inheritdoc ERC721Bridge
    85      function _initiateBridgeERC721(
    86          address _localToken,
    87          address _remoteToken,
    88          address _from,
    89          address _to,
    90          uint256 _tokenId,
    91          uint32 _minGasLimit,
    92          bytes calldata _extraData
    93      )
    94          internal
    95          override
    96      {
    97          require(_remoteToken != address(0), "L2ERC721Bridge: remote token cannot be address(0)");
    98  
    99          // Check that the withdrawal is being initiated by the NFT owner
   100          require(
   101              _from == IOptimismMintableERC721(_localToken).ownerOf(_tokenId),
   102              "L2ERC721Bridge: Withdrawal is not being initiated by NFT owner"
   103          );
   104  
   105          // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId)
   106          // slither-disable-next-line reentrancy-events
   107          address remoteToken = IOptimismMintableERC721(_localToken).remoteToken();
   108          require(remoteToken == _remoteToken, "L2ERC721Bridge: remote token does not match given value");
   109  
   110          // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2
   111          // usage
   112          // slither-disable-next-line reentrancy-events
   113          IOptimismMintableERC721(_localToken).burn(_from, _tokenId);
   114  
   115          bytes memory message = abi.encodeWithSelector(
   116              L1ERC721Bridge.finalizeBridgeERC721.selector, remoteToken, _localToken, _from, _to, _tokenId, _extraData
   117          );
   118  
   119          // Send message to L1 bridge
   120          // slither-disable-next-line reentrancy-events
   121          messenger.sendMessage({ _target: address(otherBridge), _message: message, _minGasLimit: _minGasLimit });
   122  
   123          // slither-disable-next-line reentrancy-events
   124          emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData);
   125      }
   126  }