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 }