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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { console } from "forge-std/console.sol";
     5  
     6  // Testing utilities
     7  import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
     8  
     9  // Target contract dependencies
    10  import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    11  import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
    12  import { OptimismMintableERC721 } from "src/universal/OptimismMintableERC721.sol";
    13  
    14  // Target contract
    15  import { L2ERC721Bridge } from "src/L2/L2ERC721Bridge.sol";
    16  
    17  contract TestERC721 is ERC721 {
    18      constructor() ERC721("Test", "TST") { }
    19  
    20      function mint(address to, uint256 tokenId) public {
    21          _mint(to, tokenId);
    22      }
    23  }
    24  
    25  contract TestMintableERC721 is OptimismMintableERC721 {
    26      constructor(
    27          address _bridge,
    28          address _remoteToken
    29      )
    30          OptimismMintableERC721(_bridge, 1, _remoteToken, "Test", "TST")
    31      { }
    32  
    33      function mint(address to, uint256 tokenId) public {
    34          _mint(to, tokenId);
    35      }
    36  }
    37  
    38  contract L2ERC721Bridge_Test is Bridge_Initializer {
    39      TestMintableERC721 internal localToken;
    40      TestERC721 internal remoteToken;
    41      uint256 internal constant tokenId = 1;
    42  
    43      event ERC721BridgeInitiated(
    44          address indexed localToken,
    45          address indexed remoteToken,
    46          address indexed from,
    47          address to,
    48          uint256 tokenId,
    49          bytes extraData
    50      );
    51  
    52      event ERC721BridgeFinalized(
    53          address indexed localToken,
    54          address indexed remoteToken,
    55          address indexed from,
    56          address to,
    57          uint256 tokenId,
    58          bytes extraData
    59      );
    60  
    61      /// @dev Sets up the test suite.
    62      function setUp() public override {
    63          super.setUp();
    64  
    65          remoteToken = new TestERC721();
    66          localToken = new TestMintableERC721(address(l2ERC721Bridge), address(remoteToken));
    67  
    68          // Mint alice a token.
    69          localToken.mint(alice, tokenId);
    70  
    71          // Approve the bridge to transfer the token.
    72          vm.prank(alice);
    73          localToken.approve(address(l2ERC721Bridge), tokenId);
    74      }
    75  
    76      /// @dev Tests that the constructor sets the correct variables.
    77      function test_constructor_succeeds() public {
    78          assertEq(address(l2ERC721Bridge.MESSENGER()), address(l2CrossDomainMessenger));
    79          assertEq(address(l2ERC721Bridge.OTHER_BRIDGE()), address(l1ERC721Bridge));
    80          assertEq(address(l2ERC721Bridge.messenger()), address(l2CrossDomainMessenger));
    81          assertEq(address(l2ERC721Bridge.otherBridge()), address(l1ERC721Bridge));
    82      }
    83  
    84      /// @dev Ensures that the L2ERC721Bridge is always not paused. The pausability
    85      ///      happens on L1 and not L2.
    86      function test_paused_succeeds() external {
    87          assertFalse(l2ERC721Bridge.paused());
    88      }
    89  
    90      /// @dev Tests that `bridgeERC721` correctly bridges a token and
    91      ///      burns it on the origin chain.
    92      function test_bridgeERC721_succeeds() public {
    93          // Expect a call to the messenger.
    94          vm.expectCall(
    95              address(l2CrossDomainMessenger),
    96              abi.encodeCall(
    97                  l2CrossDomainMessenger.sendMessage,
    98                  (
    99                      address(l1ERC721Bridge),
   100                      abi.encodeCall(
   101                          L2ERC721Bridge.finalizeBridgeERC721,
   102                          (address(remoteToken), address(localToken), alice, alice, tokenId, hex"5678")
   103                          ),
   104                      1234
   105                  )
   106              )
   107          );
   108  
   109          // Expect an event to be emitted.
   110          vm.expectEmit(true, true, true, true);
   111          emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   112  
   113          // Bridge the token.
   114          vm.prank(alice);
   115          l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678");
   116  
   117          // Token is burned.
   118          vm.expectRevert("ERC721: invalid token ID");
   119          localToken.ownerOf(tokenId);
   120      }
   121  
   122      /// @dev Tests that `bridgeERC721` reverts if the owner is not an EOA.
   123      function test_bridgeERC721_fromContract_reverts() external {
   124          // Bridge the token.
   125          vm.etch(alice, hex"01");
   126          vm.prank(alice);
   127          vm.expectRevert("ERC721Bridge: account is not externally owned");
   128          l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678");
   129  
   130          // Token is not locked in the bridge.
   131          assertEq(localToken.ownerOf(tokenId), alice);
   132      }
   133  
   134      /// @dev Tests that `bridgeERC721` reverts if the local token is the zero address.
   135      function test_bridgeERC721_localTokenZeroAddress_reverts() external {
   136          // Bridge the token.
   137          vm.prank(alice);
   138          vm.expectRevert(bytes(""));
   139          l2ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678");
   140  
   141          // Token is not locked in the bridge.
   142          assertEq(localToken.ownerOf(tokenId), alice);
   143      }
   144  
   145      /// @dev Tests that `bridgeERC721` reverts if the remote token is the zero address.
   146      function test_bridgeERC721_remoteTokenZeroAddress_reverts() external {
   147          // Bridge the token.
   148          vm.prank(alice);
   149          vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)");
   150          l2ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678");
   151  
   152          // Token is not locked in the bridge.
   153          assertEq(localToken.ownerOf(tokenId), alice);
   154      }
   155  
   156      /// @dev Tests that `bridgeERC721` reverts if the caller is not the token owner.
   157      function test_bridgeERC721_wrongOwner_reverts() external {
   158          // Bridge the token.
   159          vm.prank(bob);
   160          vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner");
   161          l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678");
   162  
   163          // Token is not locked in the bridge.
   164          assertEq(localToken.ownerOf(tokenId), alice);
   165      }
   166  
   167      /// @dev Tests that `bridgeERC721To` correctly bridges a token
   168      ///      and burns it on the origin chain.
   169      function test_bridgeERC721To_succeeds() external {
   170          // Expect a call to the messenger.
   171          vm.expectCall(
   172              address(l2CrossDomainMessenger),
   173              abi.encodeCall(
   174                  l2CrossDomainMessenger.sendMessage,
   175                  (
   176                      address(l1ERC721Bridge),
   177                      abi.encodeCall(
   178                          L1ERC721Bridge.finalizeBridgeERC721,
   179                          (address(remoteToken), address(localToken), alice, bob, tokenId, hex"5678")
   180                          ),
   181                      1234
   182                  )
   183              )
   184          );
   185  
   186          // Expect an event to be emitted.
   187          vm.expectEmit(true, true, true, true);
   188          emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, bob, tokenId, hex"5678");
   189  
   190          // Bridge the token.
   191          vm.prank(alice);
   192          l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678");
   193  
   194          // Token is burned.
   195          vm.expectRevert("ERC721: invalid token ID");
   196          localToken.ownerOf(tokenId);
   197      }
   198  
   199      /// @dev Tests that `bridgeERC721To` reverts if the local token is the zero address.
   200      function test_bridgeERC721To_localTokenZeroAddress_reverts() external {
   201          // Bridge the token.
   202          vm.prank(alice);
   203          vm.expectRevert(bytes(""));
   204          l2ERC721Bridge.bridgeERC721To(address(0), address(l1ERC721Bridge), bob, tokenId, 1234, hex"5678");
   205  
   206          // Token is not locked in the bridge.
   207          assertEq(localToken.ownerOf(tokenId), alice);
   208      }
   209  
   210      /// @dev Tests that `bridgeERC721To` reverts if the remote token is the zero address.
   211      function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external {
   212          // Bridge the token.
   213          vm.prank(alice);
   214          vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)");
   215          l2ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678");
   216  
   217          // Token is not locked in the bridge.
   218          assertEq(localToken.ownerOf(tokenId), alice);
   219      }
   220  
   221      /// @dev Tests that `bridgeERC721To` reverts if the caller is not the token owner.
   222      function test_bridgeERC721To_wrongOwner_reverts() external {
   223          // Bridge the token.
   224          vm.prank(bob);
   225          vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner");
   226          l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678");
   227  
   228          // Token is not locked in the bridge.
   229          assertEq(localToken.ownerOf(tokenId), alice);
   230      }
   231  
   232      /// @dev Tests that `finalizeBridgeERC721` correctly finalizes a bridged token.
   233      function test_finalizeBridgeERC721_succeeds() external {
   234          // Bridge the token.
   235          vm.prank(alice);
   236          l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678");
   237  
   238          // Expect an event to be emitted.
   239          vm.expectEmit(true, true, true, true);
   240          emit ERC721BridgeFinalized(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   241  
   242          // Finalize a withdrawal.
   243          vm.mockCall(
   244              address(l2CrossDomainMessenger),
   245              abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector),
   246              abi.encode(l1ERC721Bridge)
   247          );
   248          vm.prank(address(l2CrossDomainMessenger));
   249          l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   250  
   251          // Token is not locked in the bridge.
   252          assertEq(localToken.ownerOf(tokenId), alice);
   253      }
   254  
   255      /// @dev Tests that `finalizeBridgeERC721` reverts if the token is not compliant
   256      ///      with the `IOptimismMintableERC721` interface.
   257      function test_finalizeBridgeERC721_interfaceNotCompliant_reverts() external {
   258          // Create a non-compliant token
   259          NonCompliantERC721 nonCompliantToken = new NonCompliantERC721(alice);
   260  
   261          // Bridge the non-compliant token.
   262          vm.prank(alice);
   263          l2ERC721Bridge.bridgeERC721(address(nonCompliantToken), address(0x01), tokenId, 1234, hex"5678");
   264  
   265          // Attempt to finalize the withdrawal. Should revert because the token does not claim
   266          // to be compliant with the `IOptimismMintableERC721` interface.
   267          vm.mockCall(
   268              address(l2CrossDomainMessenger),
   269              abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector),
   270              abi.encode(l1ERC721Bridge)
   271          );
   272          vm.prank(address(l2CrossDomainMessenger));
   273          vm.expectRevert("L2ERC721Bridge: local token interface is not compliant");
   274          l2ERC721Bridge.finalizeBridgeERC721(
   275              address(address(nonCompliantToken)), address(address(0x01)), alice, alice, tokenId, hex"5678"
   276          );
   277      }
   278  
   279      /// @dev Tests that `finalizeBridgeERC721` reverts when not called by the remote bridge.
   280      function test_finalizeBridgeERC721_notViaLocalMessenger_reverts() external {
   281          // Finalize a withdrawal.
   282          vm.prank(alice);
   283          vm.expectRevert("ERC721Bridge: function can only be called from the other bridge");
   284          l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   285      }
   286  
   287      /// @dev Tests that `finalizeBridgeERC721` reverts when not called by the remote bridge.
   288      function test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() external {
   289          // Finalize a withdrawal.
   290          vm.mockCall(
   291              address(l2CrossDomainMessenger),
   292              abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector),
   293              abi.encode(alice)
   294          );
   295          vm.prank(address(l2CrossDomainMessenger));
   296          vm.expectRevert("ERC721Bridge: function can only be called from the other bridge");
   297          l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   298      }
   299  
   300      /// @dev Tests that `finalizeBridgeERC721` reverts when the local token is the
   301      ///      address of the bridge itself.
   302      function test_finalizeBridgeERC721_selfToken_reverts() external {
   303          // Finalize a withdrawal.
   304          vm.mockCall(
   305              address(l2CrossDomainMessenger),
   306              abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector),
   307              abi.encode(address(l1ERC721Bridge))
   308          );
   309          vm.prank(address(l2CrossDomainMessenger));
   310          vm.expectRevert("L2ERC721Bridge: local token cannot be self");
   311          l2ERC721Bridge.finalizeBridgeERC721(
   312              address(l2ERC721Bridge), address(remoteToken), alice, alice, tokenId, hex"5678"
   313          );
   314      }
   315  
   316      /// @dev Tests that `finalizeBridgeERC721` reverts when already finalized.
   317      function test_finalizeBridgeERC721_alreadyExists_reverts() external {
   318          // Finalize a withdrawal.
   319          vm.mockCall(
   320              address(l2CrossDomainMessenger),
   321              abi.encodeWithSelector(l2CrossDomainMessenger.xDomainMessageSender.selector),
   322              abi.encode(address(l1ERC721Bridge))
   323          );
   324          vm.prank(address(l2CrossDomainMessenger));
   325          vm.expectRevert("ERC721: token already minted");
   326          l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678");
   327      }
   328  }
   329  
   330  /// @dev A non-compliant ERC721 token that does not implement the full ERC721 interface.
   331  ///      This is used to test that the bridge will revert if the token does not claim to
   332  ///      support the ERC721 interface.
   333  contract NonCompliantERC721 {
   334      address internal immutable owner;
   335  
   336      constructor(address _owner) {
   337          owner = _owner;
   338      }
   339  
   340      function ownerOf(uint256) external view returns (address) {
   341          return owner;
   342      }
   343  
   344      function remoteToken() external pure returns (address) {
   345          return address(0x01);
   346      }
   347  
   348      function burn(address, uint256) external {
   349          // Do nothing.
   350      }
   351  
   352      function supportsInterface(bytes4) external pure returns (bool) {
   353          return false;
   354      }
   355  }