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 }