github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/L2/L2StandardBridge.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 // Testing utilities 5 import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; 6 7 // Target contract is imported by the `Bridge_Initializer` 8 import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol"; 9 import { stdStorage, StdStorage } from "forge-std/Test.sol"; 10 import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; 11 import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol"; 12 import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 13 14 // Libraries 15 import { Hashing } from "src/libraries/Hashing.sol"; 16 import { Types } from "src/libraries/Types.sol"; 17 18 // Target contract dependencies 19 import { L2StandardBridge } from "src/L2/L2StandardBridge.sol"; 20 import { Predeploys } from "src/libraries/Predeploys.sol"; 21 import { StandardBridge } from "src/universal/StandardBridge.sol"; 22 import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; 23 24 contract L2StandardBridge_Test is Bridge_Initializer { 25 using stdStorage for StdStorage; 26 27 /// @dev Test that the bridge's constructor sets the correct values. 28 function test_constructor_succeeds() external { 29 L2StandardBridge impl = 30 L2StandardBridge(payable(EIP1967Helper.getImplementation(deploy.mustGetAddress("L2StandardBridge")))); 31 assertEq(address(impl.MESSENGER()), address(0)); 32 assertEq(address(impl.messenger()), address(0)); 33 assertEq(address(impl.OTHER_BRIDGE()), address(0)); 34 assertEq(address(impl.otherBridge()), address(0)); 35 } 36 37 /// @dev Tests that the bridge is initialized correctly. 38 function test_initialize_succeeds() external { 39 assertEq(address(l2StandardBridge.MESSENGER()), address(l2CrossDomainMessenger)); 40 assertEq(address(l2StandardBridge.messenger()), address(l2CrossDomainMessenger)); 41 assertEq(l1StandardBridge.l2TokenBridge(), address(l2StandardBridge)); 42 assertEq(address(l2StandardBridge.OTHER_BRIDGE()), address(l1StandardBridge)); 43 assertEq(address(l2StandardBridge.otherBridge()), address(l1StandardBridge)); 44 } 45 46 /// @dev Ensures that the L2StandardBridge is always not paused. The pausability 47 /// happens on L1 and not L2. 48 function test_paused_succeeds() external { 49 assertFalse(l2StandardBridge.paused()); 50 } 51 52 /// @dev Tests that the bridge receives ETH and successfully initiates a withdrawal. 53 function test_receive_succeeds() external { 54 assertEq(address(l2ToL1MessagePasser).balance, 0); 55 uint256 nonce = l2CrossDomainMessenger.messageNonce(); 56 57 bytes memory message = 58 abi.encodeWithSelector(StandardBridge.finalizeBridgeETH.selector, alice, alice, 100, hex""); 59 uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 200_000); 60 bytes memory withdrawalData = abi.encodeWithSelector( 61 CrossDomainMessenger.relayMessage.selector, 62 nonce, 63 address(l2StandardBridge), 64 address(l1StandardBridge), 65 100, 66 200_000, 67 message 68 ); 69 bytes32 withdrawalHash = Hashing.hashWithdrawal( 70 Types.WithdrawalTransaction({ 71 nonce: nonce, 72 sender: address(l2CrossDomainMessenger), 73 target: address(l1CrossDomainMessenger), 74 value: 100, 75 gasLimit: baseGas, 76 data: withdrawalData 77 }) 78 ); 79 80 vm.expectEmit(true, true, true, true); 81 emit WithdrawalInitiated(address(0), Predeploys.LEGACY_ERC20_ETH, alice, alice, 100, hex""); 82 83 vm.expectEmit(true, true, true, true); 84 emit ETHBridgeInitiated(alice, alice, 100, hex""); 85 86 // L2ToL1MessagePasser will emit a MessagePassed event 87 vm.expectEmit(true, true, true, true, address(l2ToL1MessagePasser)); 88 emit MessagePassed( 89 nonce, 90 address(l2CrossDomainMessenger), 91 address(l1CrossDomainMessenger), 92 100, 93 baseGas, 94 withdrawalData, 95 withdrawalHash 96 ); 97 98 // SentMessage event emitted by the CrossDomainMessenger 99 vm.expectEmit(true, true, true, true, address(l2CrossDomainMessenger)); 100 emit SentMessage(address(l1StandardBridge), address(l2StandardBridge), message, nonce, 200_000); 101 102 // SentMessageExtension1 event emitted by the CrossDomainMessenger 103 vm.expectEmit(true, true, true, true, address(l2CrossDomainMessenger)); 104 emit SentMessageExtension1(address(l2StandardBridge), 100); 105 106 vm.expectCall( 107 address(l2CrossDomainMessenger), 108 abi.encodeWithSelector( 109 CrossDomainMessenger.sendMessage.selector, 110 address(l1StandardBridge), 111 message, 112 200_000 // StandardBridge's RECEIVE_DEFAULT_GAS_LIMIT 113 ) 114 ); 115 116 vm.expectCall( 117 Predeploys.L2_TO_L1_MESSAGE_PASSER, 118 abi.encodeWithSelector( 119 L2ToL1MessagePasser.initiateWithdrawal.selector, 120 address(l1CrossDomainMessenger), 121 baseGas, 122 withdrawalData 123 ) 124 ); 125 126 vm.prank(alice, alice); 127 (bool success,) = address(l2StandardBridge).call{ value: 100 }(hex""); 128 assertEq(success, true); 129 assertEq(address(l2ToL1MessagePasser).balance, 100); 130 } 131 132 /// @dev Tests that `withdraw` reverts if the amount is not equal to the value sent. 133 function test_withdraw_insufficientValue_reverts() external { 134 assertEq(address(l2ToL1MessagePasser).balance, 0); 135 136 vm.expectRevert("StandardBridge: bridging ETH must include sufficient ETH value"); 137 vm.prank(alice, alice); 138 l2StandardBridge.withdraw(address(Predeploys.LEGACY_ERC20_ETH), 100, 1000, hex""); 139 } 140 141 /// @dev Tests that the legacy `withdraw` interface on the L2StandardBridge 142 /// successfully initiates a withdrawal. 143 function test_withdraw_ether_succeeds() external { 144 assertTrue(alice.balance >= 100); 145 assertEq(Predeploys.L2_TO_L1_MESSAGE_PASSER.balance, 0); 146 147 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 148 emit WithdrawalInitiated({ 149 l1Token: address(0), 150 l2Token: Predeploys.LEGACY_ERC20_ETH, 151 from: alice, 152 to: alice, 153 amount: 100, 154 data: hex"" 155 }); 156 157 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 158 emit ETHBridgeInitiated({ from: alice, to: alice, amount: 100, data: hex"" }); 159 160 vm.prank(alice, alice); 161 l2StandardBridge.withdraw{ value: 100 }({ 162 _l2Token: Predeploys.LEGACY_ERC20_ETH, 163 _amount: 100, 164 _minGasLimit: 1000, 165 _extraData: hex"" 166 }); 167 168 assertEq(Predeploys.L2_TO_L1_MESSAGE_PASSER.balance, 100); 169 } 170 } 171 172 contract PreBridgeERC20 is Bridge_Initializer { 173 /// @dev Sets up expected calls and emits for a successful ERC20 withdrawal. 174 function _preBridgeERC20(bool _isLegacy, address _l2Token) internal { 175 // Alice has 100 L2Token 176 deal(_l2Token, alice, 100, true); 177 assertEq(ERC20(_l2Token).balanceOf(alice), 100); 178 uint256 nonce = l2CrossDomainMessenger.messageNonce(); 179 bytes memory message = abi.encodeWithSelector( 180 StandardBridge.finalizeBridgeERC20.selector, address(L1Token), _l2Token, alice, alice, 100, hex"" 181 ); 182 uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 1000); 183 bytes memory withdrawalData = abi.encodeWithSelector( 184 CrossDomainMessenger.relayMessage.selector, 185 nonce, 186 address(l2StandardBridge), 187 address(l1StandardBridge), 188 0, 189 1000, 190 message 191 ); 192 bytes32 withdrawalHash = Hashing.hashWithdrawal( 193 Types.WithdrawalTransaction({ 194 nonce: nonce, 195 sender: address(l2CrossDomainMessenger), 196 target: address(l1CrossDomainMessenger), 197 value: 0, 198 gasLimit: baseGas, 199 data: withdrawalData 200 }) 201 ); 202 203 if (_isLegacy) { 204 vm.expectCall( 205 address(l2StandardBridge), 206 abi.encodeWithSelector(l2StandardBridge.withdraw.selector, _l2Token, 100, 1000, hex"") 207 ); 208 } else { 209 vm.expectCall( 210 address(l2StandardBridge), 211 abi.encodeWithSelector( 212 l2StandardBridge.bridgeERC20.selector, _l2Token, address(L1Token), 100, 1000, hex"" 213 ) 214 ); 215 } 216 217 vm.expectCall( 218 address(l2CrossDomainMessenger), 219 abi.encodeWithSelector(CrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, 1000) 220 ); 221 222 vm.expectCall( 223 Predeploys.L2_TO_L1_MESSAGE_PASSER, 224 abi.encodeWithSelector( 225 L2ToL1MessagePasser.initiateWithdrawal.selector, 226 address(l1CrossDomainMessenger), 227 baseGas, 228 withdrawalData 229 ) 230 ); 231 232 // The l2StandardBridge should burn the tokens 233 vm.expectCall(_l2Token, abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)); 234 235 vm.expectEmit(true, true, true, true); 236 emit WithdrawalInitiated(address(L1Token), _l2Token, alice, alice, 100, hex""); 237 238 vm.expectEmit(true, true, true, true); 239 emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, alice, 100, hex""); 240 241 vm.expectEmit(true, true, true, true); 242 emit MessagePassed( 243 nonce, 244 address(l2CrossDomainMessenger), 245 address(l1CrossDomainMessenger), 246 0, 247 baseGas, 248 withdrawalData, 249 withdrawalHash 250 ); 251 252 // SentMessage event emitted by the CrossDomainMessenger 253 vm.expectEmit(true, true, true, true); 254 emit SentMessage(address(l1StandardBridge), address(l2StandardBridge), message, nonce, 1000); 255 256 // SentMessageExtension1 event emitted by the CrossDomainMessenger 257 vm.expectEmit(true, true, true, true); 258 emit SentMessageExtension1(address(l2StandardBridge), 0); 259 260 vm.prank(alice, alice); 261 } 262 } 263 264 contract L2StandardBridge_BridgeERC20_Test is PreBridgeERC20 { 265 // withdraw 266 // - token is burned 267 // - emits WithdrawalInitiated 268 // - calls Withdrawer.initiateWithdrawal 269 function test_withdraw_withdrawingERC20_succeeds() external { 270 _preBridgeERC20({ _isLegacy: true, _l2Token: address(L2Token) }); 271 l2StandardBridge.withdraw(address(L2Token), 100, 1000, hex""); 272 273 assertEq(L2Token.balanceOf(alice), 0); 274 } 275 276 // BridgeERC20 277 // - token is burned 278 // - emits WithdrawalInitiated 279 // - calls Withdrawer.initiateWithdrawal 280 function test_bridgeERC20_succeeds() external { 281 _preBridgeERC20({ _isLegacy: false, _l2Token: address(L2Token) }); 282 l2StandardBridge.bridgeERC20(address(L2Token), address(L1Token), 100, 1000, hex""); 283 284 assertEq(L2Token.balanceOf(alice), 0); 285 } 286 287 function test_withdrawLegacyERC20_succeeds() external { 288 _preBridgeERC20({ _isLegacy: true, _l2Token: address(LegacyL2Token) }); 289 l2StandardBridge.withdraw(address(LegacyL2Token), 100, 1000, hex""); 290 291 assertEq(L2Token.balanceOf(alice), 0); 292 } 293 294 function test_bridgeLegacyERC20_succeeds() external { 295 _preBridgeERC20({ _isLegacy: false, _l2Token: address(LegacyL2Token) }); 296 l2StandardBridge.bridgeERC20(address(LegacyL2Token), address(L1Token), 100, 1000, hex""); 297 298 assertEq(L2Token.balanceOf(alice), 0); 299 } 300 301 function test_withdraw_notEOA_reverts() external { 302 // This contract has 100 L2Token 303 deal(address(L2Token), address(this), 100, true); 304 305 vm.expectRevert("StandardBridge: function can only be called from an EOA"); 306 l2StandardBridge.withdraw(address(L2Token), 100, 1000, hex""); 307 } 308 } 309 310 contract PreBridgeERC20To is Bridge_Initializer { 311 // withdrawTo and BridgeERC20To should behave the same when transferring ERC20 tokens 312 // so they should share the same setup and expectEmit calls 313 function _preBridgeERC20To(bool _isLegacy, address _l2Token) internal { 314 deal(_l2Token, alice, 100, true); 315 assertEq(ERC20(L2Token).balanceOf(alice), 100); 316 uint256 nonce = l2CrossDomainMessenger.messageNonce(); 317 bytes memory message = abi.encodeWithSelector( 318 StandardBridge.finalizeBridgeERC20.selector, address(L1Token), _l2Token, alice, bob, 100, hex"" 319 ); 320 uint64 baseGas = l2CrossDomainMessenger.baseGas(message, 1000); 321 bytes memory withdrawalData = abi.encodeWithSelector( 322 CrossDomainMessenger.relayMessage.selector, 323 nonce, 324 address(l2StandardBridge), 325 address(l1StandardBridge), 326 0, 327 1000, 328 message 329 ); 330 bytes32 withdrawalHash = Hashing.hashWithdrawal( 331 Types.WithdrawalTransaction({ 332 nonce: nonce, 333 sender: address(l2CrossDomainMessenger), 334 target: address(l1CrossDomainMessenger), 335 value: 0, 336 gasLimit: baseGas, 337 data: withdrawalData 338 }) 339 ); 340 341 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 342 emit WithdrawalInitiated(address(L1Token), _l2Token, alice, bob, 100, hex""); 343 344 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 345 emit ERC20BridgeInitiated(_l2Token, address(L1Token), alice, bob, 100, hex""); 346 347 vm.expectEmit(true, true, true, true, address(l2ToL1MessagePasser)); 348 emit MessagePassed( 349 nonce, 350 address(l2CrossDomainMessenger), 351 address(l1CrossDomainMessenger), 352 0, 353 baseGas, 354 withdrawalData, 355 withdrawalHash 356 ); 357 358 // SentMessage event emitted by the CrossDomainMessenger 359 vm.expectEmit(true, true, true, true, address(l2CrossDomainMessenger)); 360 emit SentMessage(address(l1StandardBridge), address(l2StandardBridge), message, nonce, 1000); 361 362 // SentMessageExtension1 event emitted by the CrossDomainMessenger 363 vm.expectEmit(true, true, true, true, address(l2CrossDomainMessenger)); 364 emit SentMessageExtension1(address(l2StandardBridge), 0); 365 366 if (_isLegacy) { 367 vm.expectCall( 368 address(l2StandardBridge), 369 abi.encodeWithSelector(l2StandardBridge.withdrawTo.selector, _l2Token, bob, 100, 1000, hex"") 370 ); 371 } else { 372 vm.expectCall( 373 address(l2StandardBridge), 374 abi.encodeWithSelector( 375 l2StandardBridge.bridgeERC20To.selector, _l2Token, address(L1Token), bob, 100, 1000, hex"" 376 ) 377 ); 378 } 379 380 vm.expectCall( 381 address(l2CrossDomainMessenger), 382 abi.encodeWithSelector(CrossDomainMessenger.sendMessage.selector, address(l1StandardBridge), message, 1000) 383 ); 384 385 vm.expectCall( 386 Predeploys.L2_TO_L1_MESSAGE_PASSER, 387 abi.encodeWithSelector( 388 L2ToL1MessagePasser.initiateWithdrawal.selector, 389 address(l1CrossDomainMessenger), 390 baseGas, 391 withdrawalData 392 ) 393 ); 394 395 // The l2StandardBridge should burn the tokens 396 vm.expectCall(address(L2Token), abi.encodeWithSelector(OptimismMintableERC20.burn.selector, alice, 100)); 397 398 vm.prank(alice, alice); 399 } 400 } 401 402 contract L2StandardBridge_BridgeERC20To_Test is PreBridgeERC20To { 403 /// @dev Tests that `withdrawTo` burns the tokens, emits `WithdrawalInitiated`, 404 /// and initiates a withdrawal with `Withdrawer.initiateWithdrawal`. 405 function test_withdrawTo_withdrawingERC20_succeeds() external { 406 _preBridgeERC20To({ _isLegacy: true, _l2Token: address(L2Token) }); 407 l2StandardBridge.withdrawTo(address(L2Token), bob, 100, 1000, hex""); 408 409 assertEq(L2Token.balanceOf(alice), 0); 410 } 411 412 /// @dev Tests that `bridgeERC20To` burns the tokens, emits `WithdrawalInitiated`, 413 /// and initiates a withdrawal with `Withdrawer.initiateWithdrawal`. 414 function test_bridgeERC20To_succeeds() external { 415 _preBridgeERC20To({ _isLegacy: false, _l2Token: address(L2Token) }); 416 l2StandardBridge.bridgeERC20To(address(L2Token), address(L1Token), bob, 100, 1000, hex""); 417 assertEq(L2Token.balanceOf(alice), 0); 418 } 419 } 420 421 contract L2StandardBridge_Bridge_Test is Bridge_Initializer { 422 /// @dev Tests that `finalizeDeposit` succeeds. It should: 423 /// - only be callable by the l1TokenBridge 424 /// - emit `DepositFinalized` if the token pair is supported 425 /// - call `Withdrawer.initiateWithdrawal` if the token pair is not supported 426 function test_finalizeDeposit_depositingERC20_succeeds() external { 427 vm.mockCall( 428 address(l2StandardBridge.messenger()), 429 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 430 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 431 ); 432 433 vm.expectCall(address(L2Token), abi.encodeWithSelector(OptimismMintableERC20.mint.selector, alice, 100)); 434 435 // Should emit both the bedrock and legacy events 436 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 437 emit DepositFinalized(address(L1Token), address(L2Token), alice, alice, 100, hex""); 438 439 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 440 emit ERC20BridgeFinalized(address(L2Token), address(L1Token), alice, alice, 100, hex""); 441 442 vm.prank(address(l2CrossDomainMessenger)); 443 l2StandardBridge.finalizeDeposit(address(L1Token), address(L2Token), alice, alice, 100, hex""); 444 } 445 446 /// @dev Tests that `finalizeDeposit` succeeds when depositing ETH. 447 function test_finalizeDeposit_depositingETH_succeeds() external { 448 vm.mockCall( 449 address(l2StandardBridge.messenger()), 450 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 451 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 452 ); 453 454 // Should emit both the bedrock and legacy events 455 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 456 emit DepositFinalized(address(L1Token), address(L2Token), alice, alice, 100, hex""); 457 458 vm.expectEmit(true, true, true, true, address(l2StandardBridge)); 459 emit ERC20BridgeFinalized( 460 address(L2Token), // localToken 461 address(L1Token), // remoteToken 462 alice, 463 alice, 464 100, 465 hex"" 466 ); 467 468 vm.prank(address(l2CrossDomainMessenger)); 469 l2StandardBridge.finalizeDeposit(address(L1Token), address(L2Token), alice, alice, 100, hex""); 470 } 471 472 /// @dev Tests that `finalizeDeposit` reverts if the amounts do not match. 473 function test_finalizeBridgeETH_incorrectValue_reverts() external { 474 vm.mockCall( 475 address(l2StandardBridge.messenger()), 476 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 477 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 478 ); 479 vm.deal(address(l2CrossDomainMessenger), 100); 480 vm.prank(address(l2CrossDomainMessenger)); 481 vm.expectRevert("StandardBridge: amount sent does not match amount required"); 482 l2StandardBridge.finalizeBridgeETH{ value: 50 }(alice, alice, 100, hex""); 483 } 484 485 /// @dev Tests that `finalizeDeposit` reverts if the receipient is the other bridge. 486 function test_finalizeBridgeETH_sendToSelf_reverts() external { 487 vm.mockCall( 488 address(l2StandardBridge.messenger()), 489 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 490 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 491 ); 492 vm.deal(address(l2CrossDomainMessenger), 100); 493 vm.prank(address(l2CrossDomainMessenger)); 494 vm.expectRevert("StandardBridge: cannot send to self"); 495 l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, address(l2StandardBridge), 100, hex""); 496 } 497 498 /// @dev Tests that `finalizeDeposit` reverts if the receipient is the messenger. 499 function test_finalizeBridgeETH_sendToMessenger_reverts() external { 500 vm.mockCall( 501 address(l2StandardBridge.messenger()), 502 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 503 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 504 ); 505 vm.deal(address(l2CrossDomainMessenger), 100); 506 vm.prank(address(l2CrossDomainMessenger)); 507 vm.expectRevert("StandardBridge: cannot send to messenger"); 508 l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, address(l2CrossDomainMessenger), 100, hex""); 509 } 510 } 511 512 contract L2StandardBridge_FinalizeBridgeETH_Test is Bridge_Initializer { 513 /// @dev Tests that `finalizeBridgeETH` succeeds. 514 function test_finalizeBridgeETH_succeeds() external { 515 address messenger = address(l2StandardBridge.messenger()); 516 vm.mockCall( 517 messenger, 518 abi.encodeWithSelector(CrossDomainMessenger.xDomainMessageSender.selector), 519 abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) 520 ); 521 vm.deal(messenger, 100); 522 vm.prank(messenger); 523 524 vm.expectEmit(true, true, true, true); 525 emit DepositFinalized(address(0), Predeploys.LEGACY_ERC20_ETH, alice, alice, 100, hex""); 526 527 vm.expectEmit(true, true, true, true); 528 emit ETHBridgeFinalized(alice, alice, 100, hex""); 529 530 l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, alice, 100, hex""); 531 } 532 }