github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 // Testing utilities 5 import { stdError } from "forge-std/Test.sol"; 6 7 import { CommonTest } from "test/setup/CommonTest.sol"; 8 import { NextImpl } from "test/mocks/NextImpl.sol"; 9 import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; 10 11 // Libraries 12 import { Types } from "src/libraries/Types.sol"; 13 import { Hashing } from "src/libraries/Hashing.sol"; 14 import { Constants } from "src/libraries/Constants.sol"; 15 16 // Target contract dependencies 17 import { Proxy } from "src/universal/Proxy.sol"; 18 import { ResourceMetering } from "src/L1/ResourceMetering.sol"; 19 import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; 20 import { SystemConfig } from "src/L1/SystemConfig.sol"; 21 import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; 22 import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; 23 24 import { FaultDisputeGame, IDisputeGame } from "src/dispute/FaultDisputeGame.sol"; 25 import "src/libraries/DisputeTypes.sol"; 26 27 contract OptimismPortal2_Test is CommonTest { 28 address depositor; 29 30 function setUp() public override { 31 super.enableFaultProofs(); 32 super.setUp(); 33 34 depositor = makeAddr("depositor"); 35 } 36 37 /// @dev Tests that the constructor sets the correct values. 38 /// @notice Marked virtual to be overridden in 39 /// test/kontrol/deployment/DeploymentSummary.t.sol 40 function test_constructor_succeeds() external virtual { 41 OptimismPortal2 opImpl = OptimismPortal2(payable(deploy.mustGetAddress("OptimismPortal2"))); 42 assertEq(address(opImpl.disputeGameFactory()), address(0)); 43 assertEq(address(opImpl.SYSTEM_CONFIG()), address(0)); 44 assertEq(address(opImpl.systemConfig()), address(0)); 45 assertEq(address(opImpl.superchainConfig()), address(0)); 46 assertEq(opImpl.l2Sender(), Constants.DEFAULT_L2_SENDER); 47 assertEq(opImpl.respectedGameType().raw(), deploy.cfg().respectedGameType()); 48 } 49 50 /// @dev Tests that the initializer sets the correct values. 51 /// @notice Marked virtual to be overridden in 52 /// test/kontrol/deployment/DeploymentSummary.t.sol 53 function test_initialize_succeeds() external virtual { 54 address guardian = deploy.cfg().superchainConfigGuardian(); 55 assertEq(address(optimismPortal2.disputeGameFactory()), address(disputeGameFactory)); 56 assertEq(address(optimismPortal2.SYSTEM_CONFIG()), address(systemConfig)); 57 assertEq(address(optimismPortal2.systemConfig()), address(systemConfig)); 58 assertEq(optimismPortal2.GUARDIAN(), guardian); 59 assertEq(optimismPortal2.guardian(), guardian); 60 assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); 61 assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); 62 assertEq(optimismPortal2.paused(), false); 63 assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); 64 } 65 66 /// @dev Tests that `pause` successfully pauses 67 /// when called by the GUARDIAN. 68 function test_pause_succeeds() external { 69 address guardian = optimismPortal2.GUARDIAN(); 70 71 assertEq(optimismPortal2.paused(), false); 72 73 vm.expectEmit(address(superchainConfig)); 74 emit Paused("identifier"); 75 76 vm.prank(guardian); 77 superchainConfig.pause("identifier"); 78 79 assertEq(optimismPortal2.paused(), true); 80 } 81 82 /// @dev Tests that `pause` reverts when called by a non-GUARDIAN. 83 function test_pause_onlyGuardian_reverts() external { 84 assertEq(optimismPortal2.paused(), false); 85 86 assertTrue(optimismPortal2.GUARDIAN() != alice); 87 vm.expectRevert("SuperchainConfig: only guardian can pause"); 88 vm.prank(alice); 89 superchainConfig.pause("identifier"); 90 91 assertEq(optimismPortal2.paused(), false); 92 } 93 94 /// @dev Tests that `unpause` successfully unpauses 95 /// when called by the GUARDIAN. 96 function test_unpause_succeeds() external { 97 address guardian = optimismPortal2.GUARDIAN(); 98 99 vm.prank(guardian); 100 superchainConfig.pause("identifier"); 101 assertEq(optimismPortal2.paused(), true); 102 103 vm.expectEmit(address(superchainConfig)); 104 emit Unpaused(); 105 vm.prank(guardian); 106 superchainConfig.unpause(); 107 108 assertEq(optimismPortal2.paused(), false); 109 } 110 111 /// @dev Tests that `unpause` reverts when called by a non-GUARDIAN. 112 function test_unpause_onlyGuardian_reverts() external { 113 address guardian = optimismPortal2.GUARDIAN(); 114 115 vm.prank(guardian); 116 superchainConfig.pause("identifier"); 117 assertEq(optimismPortal2.paused(), true); 118 119 assertTrue(optimismPortal2.GUARDIAN() != alice); 120 vm.expectRevert("SuperchainConfig: only guardian can unpause"); 121 vm.prank(alice); 122 superchainConfig.unpause(); 123 124 assertEq(optimismPortal2.paused(), true); 125 } 126 127 /// @dev Tests that `receive` successdully deposits ETH. 128 function testFuzz_receive_succeeds(uint256 _value) external { 129 vm.expectEmit(address(optimismPortal2)); 130 emitTransactionDeposited({ 131 _from: alice, 132 _to: alice, 133 _value: _value, 134 _mint: _value, 135 _gasLimit: 100_000, 136 _isCreation: false, 137 _data: hex"" 138 }); 139 140 // give alice money and send as an eoa 141 vm.deal(alice, _value); 142 vm.prank(alice, alice); 143 (bool s,) = address(optimismPortal2).call{ value: _value }(hex""); 144 145 assertTrue(s); 146 assertEq(address(optimismPortal2).balance, _value); 147 } 148 149 /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero 150 /// for a contract creation deposit. 151 function test_depositTransaction_contractCreation_reverts() external { 152 // contract creation must have a target of address(0) 153 vm.expectRevert("OptimismPortal: must send to address(0) when creating a contract"); 154 optimismPortal2.depositTransaction(address(1), 1, 0, true, hex""); 155 } 156 157 /// @dev Tests that `depositTransaction` reverts when the data is too large. 158 /// This places an upper bound on unsafe blocks sent over p2p. 159 function test_depositTransaction_largeData_reverts() external { 160 uint256 size = 120_001; 161 uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(size)); 162 vm.expectRevert("OptimismPortal: data too large"); 163 optimismPortal2.depositTransaction({ 164 _to: address(0), 165 _value: 0, 166 _gasLimit: gasLimit, 167 _isCreation: false, 168 _data: new bytes(size) 169 }); 170 } 171 172 /// @dev Tests that `depositTransaction` reverts when the gas limit is too small. 173 function test_depositTransaction_smallGasLimit_reverts() external { 174 vm.expectRevert("OptimismPortal: gas limit too small"); 175 optimismPortal2.depositTransaction({ _to: address(1), _value: 0, _gasLimit: 0, _isCreation: false, _data: hex"" }); 176 } 177 178 /// @dev Tests that `depositTransaction` succeeds for small, 179 /// but sufficient, gas limits. 180 function testFuzz_depositTransaction_smallGasLimit_succeeds(bytes memory _data, bool _shouldFail) external { 181 uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length)); 182 if (_shouldFail) { 183 gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1)); 184 vm.expectRevert("OptimismPortal: gas limit too small"); 185 } 186 187 optimismPortal2.depositTransaction({ 188 _to: address(0x40), 189 _value: 0, 190 _gasLimit: gasLimit, 191 _isCreation: false, 192 _data: _data 193 }); 194 } 195 196 /// @dev Tests that `minimumGasLimit` succeeds for small calldata sizes. 197 /// The gas limit should be 21k for 0 calldata and increase linearly 198 /// for larger calldata sizes. 199 function test_minimumGasLimit_succeeds() external { 200 assertEq(optimismPortal2.minimumGasLimit(0), 21_000); 201 assertTrue(optimismPortal2.minimumGasLimit(2) > optimismPortal2.minimumGasLimit(1)); 202 assertTrue(optimismPortal2.minimumGasLimit(3) > optimismPortal2.minimumGasLimit(2)); 203 } 204 205 /// @dev Tests that `depositTransaction` succeeds for an EOA. 206 function testFuzz_depositTransaction_eoa_succeeds( 207 address _to, 208 uint64 _gasLimit, 209 uint256 _value, 210 uint256 _mint, 211 bool _isCreation, 212 bytes memory _data 213 ) 214 external 215 { 216 _gasLimit = uint64( 217 bound( 218 _gasLimit, 219 optimismPortal2.minimumGasLimit(uint64(_data.length)), 220 systemConfig.resourceConfig().maxResourceLimit 221 ) 222 ); 223 if (_isCreation) _to = address(0); 224 225 // EOA emulation 226 vm.expectEmit(address(optimismPortal2)); 227 emitTransactionDeposited({ 228 _from: depositor, 229 _to: _to, 230 _value: _value, 231 _mint: _mint, 232 _gasLimit: _gasLimit, 233 _isCreation: _isCreation, 234 _data: _data 235 }); 236 237 vm.deal(depositor, _mint); 238 vm.prank(depositor, depositor); 239 optimismPortal2.depositTransaction{ value: _mint }({ 240 _to: _to, 241 _value: _value, 242 _gasLimit: _gasLimit, 243 _isCreation: _isCreation, 244 _data: _data 245 }); 246 assertEq(address(optimismPortal2).balance, _mint); 247 } 248 249 /// @dev Tests that `depositTransaction` succeeds for a contract. 250 function testFuzz_depositTransaction_contract_succeeds( 251 address _to, 252 uint64 _gasLimit, 253 uint256 _value, 254 uint256 _mint, 255 bool _isCreation, 256 bytes memory _data 257 ) 258 external 259 { 260 _gasLimit = uint64( 261 bound( 262 _gasLimit, 263 optimismPortal2.minimumGasLimit(uint64(_data.length)), 264 systemConfig.resourceConfig().maxResourceLimit 265 ) 266 ); 267 if (_isCreation) _to = address(0); 268 269 vm.expectEmit(address(optimismPortal2)); 270 emitTransactionDeposited({ 271 _from: AddressAliasHelper.applyL1ToL2Alias(address(this)), 272 _to: _to, 273 _value: _value, 274 _mint: _mint, 275 _gasLimit: _gasLimit, 276 _isCreation: _isCreation, 277 _data: _data 278 }); 279 280 vm.deal(address(this), _mint); 281 vm.prank(address(this)); 282 optimismPortal2.depositTransaction{ value: _mint }({ 283 _to: _to, 284 _value: _value, 285 _gasLimit: _gasLimit, 286 _isCreation: _isCreation, 287 _data: _data 288 }); 289 assertEq(address(optimismPortal2).balance, _mint); 290 } 291 } 292 293 contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { 294 // Reusable default values for a test withdrawal 295 Types.WithdrawalTransaction _defaultTx; 296 297 FaultDisputeGame game; 298 uint256 _proposedGameIndex; 299 uint256 _proposedBlockNumber; 300 bytes32 _stateRoot; 301 bytes32 _storageRoot; 302 bytes32 _outputRoot; 303 bytes32 _withdrawalHash; 304 bytes[] _withdrawalProof; 305 Types.OutputRootProof internal _outputRootProof; 306 307 // Use a constructor to set the storage vars above, so as to minimize the number of ffi calls. 308 constructor() { 309 super.enableFaultProofs(); 310 super.setUp(); 311 312 _defaultTx = Types.WithdrawalTransaction({ 313 nonce: 0, 314 sender: alice, 315 target: bob, 316 value: 100, 317 gasLimit: 100_000, 318 data: hex"" 319 }); 320 // Get withdrawal proof data we can use for testing. 321 (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) = 322 ffi.getProveWithdrawalTransactionInputs(_defaultTx); 323 324 // Setup a dummy output root proof for reuse. 325 _outputRootProof = Types.OutputRootProof({ 326 version: bytes32(uint256(0)), 327 stateRoot: _stateRoot, 328 messagePasserStorageRoot: _storageRoot, 329 latestBlockhash: bytes32(uint256(0)) 330 }); 331 } 332 333 /// @dev Setup the system for a ready-to-use state. 334 function setUp() public override { 335 _proposedBlockNumber = 0xFF; 336 game = FaultDisputeGame( 337 payable( 338 address( 339 disputeGameFactory.create( 340 optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber) 341 ) 342 ) 343 ) 344 ); 345 _proposedGameIndex = disputeGameFactory.gameCount() - 1; 346 347 // Warp beyond the chess clocks and finalize the game. 348 vm.warp(block.timestamp + game.gameDuration().raw() / 2 + 1 seconds); 349 350 // Fund the portal so that we can withdraw ETH. 351 vm.deal(address(optimismPortal2), 0xFFFFFFFF); 352 } 353 354 /// @dev Asserts that the reentrant call will revert. 355 function callPortalAndExpectRevert() external payable { 356 vm.expectRevert("OptimismPortal: can only trigger one withdrawal per transaction"); 357 // Arguments here don't matter, as the require check is the first thing that happens. 358 // We assume that this has already been proven. 359 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 360 // Assert that the withdrawal was not finalized. 361 assertFalse(optimismPortal2.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx))); 362 } 363 364 /// @dev Tests that `blacklistDisputeGame` reverts when called by a non-guardian. 365 function testFuzz_blacklist_onlyGuardian_reverts(address _act) external { 366 vm.assume(_act != address(optimismPortal2.guardian())); 367 368 vm.expectRevert("OptimismPortal: only the guardian can blacklist dispute games"); 369 optimismPortal2.blacklistDisputeGame(IDisputeGame(address(0xdead))); 370 } 371 372 /// @dev Tests that the guardian role can blacklist any dispute game. 373 function testFuzz_blacklist_guardian_succeeds(address _addr) external { 374 vm.prank(optimismPortal2.guardian()); 375 optimismPortal2.blacklistDisputeGame(IDisputeGame(_addr)); 376 377 assertTrue(optimismPortal2.disputeGameBlacklist(IDisputeGame(_addr))); 378 } 379 380 /// @dev Tests that `setRespectedGameType` reverts when called by a non-guardian. 381 function testFuzz_setRespectedGameType_onlyGuardian_reverts(address _act, GameType _ty) external { 382 vm.assume(_act != address(optimismPortal2.guardian())); 383 384 vm.prank(_act); 385 vm.expectRevert("OptimismPortal: only the guardian can set the respected game type"); 386 optimismPortal2.setRespectedGameType(_ty); 387 } 388 389 /// @dev Tests that the guardian role can set the respected game type to anything they want. 390 function testFuzz_setRespectedGameType_guardian_succeeds(GameType _ty) external { 391 vm.prank(optimismPortal2.guardian()); 392 optimismPortal2.setRespectedGameType(_ty); 393 394 assertEq(optimismPortal2.respectedGameType().raw(), _ty.raw()); 395 } 396 397 /// @dev Tests that `proveWithdrawalTransaction` reverts when paused. 398 function test_proveWithdrawalTransaction_paused_reverts() external { 399 vm.prank(optimismPortal2.GUARDIAN()); 400 superchainConfig.pause("identifier"); 401 402 vm.expectRevert("OptimismPortal: paused"); 403 optimismPortal2.proveWithdrawalTransaction({ 404 _tx: _defaultTx, 405 _disputeGameIndex: _proposedGameIndex, 406 _outputRootProof: _outputRootProof, 407 _withdrawalProof: _withdrawalProof 408 }); 409 } 410 411 /// @dev Tests that `proveWithdrawalTransaction` reverts when the target is the portal contract. 412 function test_proveWithdrawalTransaction_onSelfCall_reverts() external { 413 _defaultTx.target = address(optimismPortal2); 414 vm.expectRevert("OptimismPortal: you cannot send messages to the portal contract"); 415 optimismPortal2.proveWithdrawalTransaction({ 416 _tx: _defaultTx, 417 _disputeGameIndex: _proposedGameIndex, 418 _outputRootProof: _outputRootProof, 419 _withdrawalProof: _withdrawalProof 420 }); 421 } 422 423 /// @dev Tests that `proveWithdrawalTransaction` reverts when the outputRootProof does not match the output root 424 function test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() external { 425 // Modify the version to invalidate the withdrawal proof. 426 _outputRootProof.version = bytes32(uint256(1)); 427 vm.expectRevert("OptimismPortal: invalid output root proof"); 428 optimismPortal2.proveWithdrawalTransaction({ 429 _tx: _defaultTx, 430 _disputeGameIndex: _proposedGameIndex, 431 _outputRootProof: _outputRootProof, 432 _withdrawalProof: _withdrawalProof 433 }); 434 } 435 436 /// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal is missing. 437 function test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() external { 438 // modify the default test values to invalidate the proof. 439 _defaultTx.data = hex"abcd"; 440 vm.expectRevert("MerkleTrie: path remainder must share all nibbles with key"); 441 optimismPortal2.proveWithdrawalTransaction({ 442 _tx: _defaultTx, 443 _disputeGameIndex: _proposedGameIndex, 444 _outputRootProof: _outputRootProof, 445 _withdrawalProof: _withdrawalProof 446 }); 447 } 448 449 /// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal has already 450 /// been proven. 451 function test_proveWithdrawalTransaction_replayProve_reverts() external { 452 vm.expectEmit(true, true, true, true); 453 emit WithdrawalProven(_withdrawalHash, alice, bob); 454 optimismPortal2.proveWithdrawalTransaction({ 455 _tx: _defaultTx, 456 _disputeGameIndex: _proposedGameIndex, 457 _outputRootProof: _outputRootProof, 458 _withdrawalProof: _withdrawalProof 459 }); 460 461 vm.expectRevert( 462 "OptimismPortal: withdrawal hash has already been proven, and the old dispute game is not invalid" 463 ); 464 optimismPortal2.proveWithdrawalTransaction({ 465 _tx: _defaultTx, 466 _disputeGameIndex: _proposedGameIndex, 467 _outputRootProof: _outputRootProof, 468 _withdrawalProof: _withdrawalProof 469 }); 470 } 471 472 /// @dev Tests that `proveWithdrawalTransaction` reverts if the dispute game being proven against is not of the 473 /// respected game type. 474 function test_proveWithdrawalTransaction_badGameType_reverts() external { 475 vm.mockCall( 476 address(disputeGameFactory), 477 abi.encodeCall(disputeGameFactory.gameAtIndex, (_proposedGameIndex)), 478 abi.encode(GameType.wrap(0xFF), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(game))) 479 ); 480 481 vm.expectRevert("OptimismPortal: invalid game type"); 482 optimismPortal2.proveWithdrawalTransaction({ 483 _tx: _defaultTx, 484 _disputeGameIndex: _proposedGameIndex, 485 _outputRootProof: _outputRootProof, 486 _withdrawalProof: _withdrawalProof 487 }); 488 } 489 490 /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has been 491 /// blacklisted. 492 function test_proveWithdrawalTransaction_replayProveBlacklisted_suceeds() external { 493 vm.expectEmit(true, true, true, true); 494 emit WithdrawalProven(_withdrawalHash, alice, bob); 495 optimismPortal2.proveWithdrawalTransaction({ 496 _tx: _defaultTx, 497 _disputeGameIndex: _proposedGameIndex, 498 _outputRootProof: _outputRootProof, 499 _withdrawalProof: _withdrawalProof 500 }); 501 502 // Blacklist the dispute dispute game. 503 vm.prank(optimismPortal2.guardian()); 504 optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); 505 506 vm.expectEmit(true, true, true, true); 507 emit WithdrawalProven(_withdrawalHash, alice, bob); 508 optimismPortal2.proveWithdrawalTransaction({ 509 _tx: _defaultTx, 510 _disputeGameIndex: _proposedGameIndex, 511 _outputRootProof: _outputRootProof, 512 _withdrawalProof: _withdrawalProof 513 }); 514 } 515 516 /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has resolved 517 /// against the favor of the root claim. 518 function test_proveWithdrawalTransaction_replayProveBadProposal_suceeds() external { 519 vm.expectEmit(true, true, true, true); 520 emit WithdrawalProven(_withdrawalHash, alice, bob); 521 optimismPortal2.proveWithdrawalTransaction({ 522 _tx: _defaultTx, 523 _disputeGameIndex: _proposedGameIndex, 524 _outputRootProof: _outputRootProof, 525 _withdrawalProof: _withdrawalProof 526 }); 527 528 vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); 529 530 vm.expectEmit(true, true, true, true); 531 emit WithdrawalProven(_withdrawalHash, alice, bob); 532 optimismPortal2.proveWithdrawalTransaction({ 533 _tx: _defaultTx, 534 _disputeGameIndex: _proposedGameIndex, 535 _outputRootProof: _outputRootProof, 536 _withdrawalProof: _withdrawalProof 537 }); 538 } 539 540 /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against is no longer 541 /// of the respected game type. 542 function test_proveWithdrawalTransaction_replayRespectedGameTypeChanged_suceeds() external { 543 // Prove the withdrawal against a game with the current respected game type. 544 vm.expectEmit(true, true, true, true); 545 emit WithdrawalProven(_withdrawalHash, alice, bob); 546 optimismPortal2.proveWithdrawalTransaction({ 547 _tx: _defaultTx, 548 _disputeGameIndex: _proposedGameIndex, 549 _outputRootProof: _outputRootProof, 550 _withdrawalProof: _withdrawalProof 551 }); 552 553 // Update the respected game type to 0xbeef. 554 vm.prank(optimismPortal2.guardian()); 555 optimismPortal2.setRespectedGameType(GameType.wrap(0xbeef)); 556 557 // Create a new game and mock the game type as 0xbeef in the factory. 558 IDisputeGame newGame = 559 disputeGameFactory.create(GameType.wrap(0), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1)); 560 vm.mockCall( 561 address(disputeGameFactory), 562 abi.encodeCall(disputeGameFactory.gameAtIndex, (_proposedGameIndex + 1)), 563 abi.encode(GameType.wrap(0xbeef), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(newGame))) 564 ); 565 566 // Re-proving should be successful against the new game. 567 vm.expectEmit(true, true, true, true); 568 emit WithdrawalProven(_withdrawalHash, alice, bob); 569 optimismPortal2.proveWithdrawalTransaction({ 570 _tx: _defaultTx, 571 _disputeGameIndex: _proposedGameIndex + 1, 572 _outputRootProof: _outputRootProof, 573 _withdrawalProof: _withdrawalProof 574 }); 575 } 576 577 /// @dev Tests that `proveWithdrawalTransaction` succeeds. 578 function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external { 579 vm.expectEmit(true, true, true, true); 580 emit WithdrawalProven(_withdrawalHash, alice, bob); 581 optimismPortal2.proveWithdrawalTransaction({ 582 _tx: _defaultTx, 583 _disputeGameIndex: _proposedGameIndex, 584 _outputRootProof: _outputRootProof, 585 _withdrawalProof: _withdrawalProof 586 }); 587 } 588 589 /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. 590 function test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() external { 591 uint256 bobBalanceBefore = address(bob).balance; 592 593 vm.expectEmit(true, true, true, true); 594 emit WithdrawalProven(_withdrawalHash, alice, bob); 595 optimismPortal2.proveWithdrawalTransaction({ 596 _tx: _defaultTx, 597 _disputeGameIndex: _proposedGameIndex, 598 _outputRootProof: _outputRootProof, 599 _withdrawalProof: _withdrawalProof 600 }); 601 602 // Warp and resolve the dispute game. 603 game.resolveClaim(0); 604 game.resolve(); 605 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); 606 607 vm.expectEmit(true, true, false, true); 608 emit WithdrawalFinalized(_withdrawalHash, true); 609 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 610 611 assert(address(bob).balance == bobBalanceBefore + 100); 612 } 613 614 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the contract is paused. 615 function test_finalizeWithdrawalTransaction_paused_reverts() external { 616 vm.prank(optimismPortal2.GUARDIAN()); 617 superchainConfig.pause("identifier"); 618 619 vm.expectRevert("OptimismPortal: paused"); 620 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 621 } 622 623 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been 624 function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external { 625 uint256 bobBalanceBefore = address(bob).balance; 626 627 vm.expectRevert("OptimismPortal: withdrawal has not been proven yet"); 628 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 629 630 assert(address(bob).balance == bobBalanceBefore); 631 } 632 633 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been 634 /// proven long enough ago. 635 function test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() external { 636 uint256 bobBalanceBefore = address(bob).balance; 637 638 vm.expectEmit(true, true, true, true); 639 emit WithdrawalProven(_withdrawalHash, alice, bob); 640 optimismPortal2.proveWithdrawalTransaction({ 641 _tx: _defaultTx, 642 _disputeGameIndex: _proposedGameIndex, 643 _outputRootProof: _outputRootProof, 644 _withdrawalProof: _withdrawalProof 645 }); 646 647 vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); 648 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 649 650 assert(address(bob).balance == bobBalanceBefore); 651 } 652 653 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the provenWithdrawal's timestamp 654 /// is less than the dispute game's creation timestamp. 655 function test_finalizeWithdrawalTransaction_timestampLessThanGameCreation_reverts() external { 656 uint256 bobBalanceBefore = address(bob).balance; 657 658 // Prove our withdrawal 659 vm.expectEmit(true, true, true, true); 660 emit WithdrawalProven(_withdrawalHash, alice, bob); 661 optimismPortal2.proveWithdrawalTransaction({ 662 _tx: _defaultTx, 663 _disputeGameIndex: _proposedGameIndex, 664 _outputRootProof: _outputRootProof, 665 _withdrawalProof: _withdrawalProof 666 }); 667 668 // Warp to after the finalization period 669 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 670 671 // Mock a createdAt change in the dispute game. 672 vm.mockCall(address(game), abi.encodeWithSignature("createdAt()"), abi.encode(block.timestamp + 1)); 673 674 // Attempt to finalize the withdrawal 675 vm.expectRevert("OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"); 676 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 677 678 // Ensure that bob's balance has remained the same 679 assertEq(bobBalanceBefore, address(bob).balance); 680 } 681 682 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the dispute game has not resolved in favor of the 683 /// root claim. 684 function test_finalizeWithdrawalTransaction_ifDisputeGameNotResolved_reverts() external { 685 uint256 bobBalanceBefore = address(bob).balance; 686 687 // Prove our withdrawal 688 vm.expectEmit(true, true, true, true); 689 emit WithdrawalProven(_withdrawalHash, alice, bob); 690 optimismPortal2.proveWithdrawalTransaction({ 691 _tx: _defaultTx, 692 _disputeGameIndex: _proposedGameIndex, 693 _outputRootProof: _outputRootProof, 694 _withdrawalProof: _withdrawalProof 695 }); 696 697 // Warp to after the finalization period 698 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 699 700 // Attempt to finalize the withdrawal 701 vm.expectRevert("OptimismPortal: output proposal has not been finalized yet"); 702 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 703 704 // Ensure that bob's balance has remained the same 705 assertEq(bobBalanceBefore, address(bob).balance); 706 } 707 708 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts. 709 function test_finalizeWithdrawalTransaction_targetFails_fails() external { 710 uint256 bobBalanceBefore = address(bob).balance; 711 vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. 712 713 vm.expectEmit(true, true, true, true); 714 emit WithdrawalProven(_withdrawalHash, alice, bob); 715 optimismPortal2.proveWithdrawalTransaction({ 716 _tx: _defaultTx, 717 _disputeGameIndex: _proposedGameIndex, 718 _outputRootProof: _outputRootProof, 719 _withdrawalProof: _withdrawalProof 720 }); 721 722 // Resolve the dispute game. 723 game.resolveClaim(0); 724 game.resolve(); 725 726 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 727 vm.expectEmit(true, true, true, true); 728 emit WithdrawalFinalized(_withdrawalHash, false); 729 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 730 731 assert(address(bob).balance == bobBalanceBefore); 732 } 733 734 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has already been 735 /// finalized. 736 function test_finalizeWithdrawalTransaction_onReplay_reverts() external { 737 vm.expectEmit(true, true, true, true); 738 emit WithdrawalProven(_withdrawalHash, alice, bob); 739 optimismPortal2.proveWithdrawalTransaction({ 740 _tx: _defaultTx, 741 _disputeGameIndex: _proposedGameIndex, 742 _outputRootProof: _outputRootProof, 743 _withdrawalProof: _withdrawalProof 744 }); 745 746 // Resolve the dispute game. 747 game.resolveClaim(0); 748 game.resolve(); 749 750 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 751 vm.expectEmit(true, true, true, true); 752 emit WithdrawalFinalized(_withdrawalHash, true); 753 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 754 755 vm.expectRevert("OptimismPortal: withdrawal has already been finalized"); 756 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 757 } 758 759 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal transaction 760 /// does not have enough gas to execute. 761 function test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() external { 762 // This number was identified through trial and error. 763 uint256 gasLimit = 150_000; 764 Types.WithdrawalTransaction memory insufficientGasTx = Types.WithdrawalTransaction({ 765 nonce: 0, 766 sender: alice, 767 target: bob, 768 value: 100, 769 gasLimit: gasLimit, 770 data: hex"" 771 }); 772 773 // Get updated proof inputs. 774 (bytes32 stateRoot, bytes32 storageRoot,,, bytes[] memory withdrawalProof) = 775 ffi.getProveWithdrawalTransactionInputs(insufficientGasTx); 776 Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({ 777 version: bytes32(0), 778 stateRoot: stateRoot, 779 messagePasserStorageRoot: storageRoot, 780 latestBlockhash: bytes32(0) 781 }); 782 783 vm.mockCall( 784 address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(Hashing.hashOutputRootProof(outputRootProof)) 785 ); 786 787 optimismPortal2.proveWithdrawalTransaction({ 788 _tx: insufficientGasTx, 789 _disputeGameIndex: _proposedGameIndex, 790 _outputRootProof: outputRootProof, 791 _withdrawalProof: withdrawalProof 792 }); 793 794 // Resolve the dispute game. 795 game.resolveClaim(0); 796 game.resolve(); 797 798 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 799 vm.expectRevert("SafeCall: Not enough gas"); 800 optimismPortal2.finalizeWithdrawalTransaction{ gas: gasLimit }(insufficientGasTx); 801 } 802 803 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if a sub-call attempts to finalize 804 /// another withdrawal. 805 function test_finalizeWithdrawalTransaction_onReentrancy_reverts() external { 806 uint256 bobBalanceBefore = address(bob).balance; 807 808 // Copy and modify the default test values to attempt a reentrant call by first calling to 809 // this contract's callPortalAndExpectRevert() function above. 810 Types.WithdrawalTransaction memory _testTx = _defaultTx; 811 _testTx.target = address(this); 812 _testTx.data = abi.encodeWithSelector(this.callPortalAndExpectRevert.selector); 813 814 // Get modified proof inputs. 815 ( 816 bytes32 stateRoot, 817 bytes32 storageRoot, 818 bytes32 outputRoot, 819 bytes32 withdrawalHash, 820 bytes[] memory withdrawalProof 821 ) = ffi.getProveWithdrawalTransactionInputs(_testTx); 822 Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({ 823 version: bytes32(0), 824 stateRoot: stateRoot, 825 messagePasserStorageRoot: storageRoot, 826 latestBlockhash: bytes32(0) 827 }); 828 829 // Return a mock output root from the game. 830 vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); 831 832 vm.expectEmit(true, true, true, true); 833 emit WithdrawalProven(withdrawalHash, alice, address(this)); 834 optimismPortal2.proveWithdrawalTransaction(_testTx, _proposedGameIndex, outputRootProof, withdrawalProof); 835 836 // Resolve the dispute game. 837 game.resolveClaim(0); 838 game.resolve(); 839 840 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 841 vm.expectCall(address(this), _testTx.data); 842 vm.expectEmit(true, true, true, true); 843 emit WithdrawalFinalized(withdrawalHash, true); 844 optimismPortal2.finalizeWithdrawalTransaction(_testTx); 845 846 // Ensure that bob's balance was not changed by the reentrant call. 847 assert(address(bob).balance == bobBalanceBefore); 848 } 849 850 /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. 851 function testDiff_finalizeWithdrawalTransaction_succeeds( 852 address _sender, 853 address _target, 854 uint256 _value, 855 uint256 _gasLimit, 856 bytes memory _data 857 ) 858 external 859 { 860 vm.assume( 861 _target != address(optimismPortal2) // Cannot call the optimism portal or a contract 862 && _target.code.length == 0 // No accounts with code 863 && _target != CONSOLE // The console has no code but behaves like a contract 864 && uint160(_target) > 9 // No precompiles (or zero address) 865 ); 866 867 // Total ETH supply is currently about 120M ETH. 868 uint256 value = bound(_value, 0, 200_000_000 ether); 869 vm.deal(address(optimismPortal2), value); 870 871 uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); 872 uint256 nonce = l2ToL1MessagePasser.messageNonce(); 873 874 // Get a withdrawal transaction and mock proof from the differential testing script. 875 Types.WithdrawalTransaction memory _tx = Types.WithdrawalTransaction({ 876 nonce: nonce, 877 sender: _sender, 878 target: _target, 879 value: value, 880 gasLimit: gasLimit, 881 data: _data 882 }); 883 ( 884 bytes32 stateRoot, 885 bytes32 storageRoot, 886 bytes32 outputRoot, 887 bytes32 withdrawalHash, 888 bytes[] memory withdrawalProof 889 ) = ffi.getProveWithdrawalTransactionInputs(_tx); 890 891 // Create the output root proof 892 Types.OutputRootProof memory proof = Types.OutputRootProof({ 893 version: bytes32(uint256(0)), 894 stateRoot: stateRoot, 895 messagePasserStorageRoot: storageRoot, 896 latestBlockhash: bytes32(uint256(0)) 897 }); 898 899 // Ensure the values returned from ffi are correct 900 assertEq(outputRoot, Hashing.hashOutputRootProof(proof)); 901 assertEq(withdrawalHash, Hashing.hashWithdrawal(_tx)); 902 903 // Setup the dispute game to return the output root 904 vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); 905 906 // Prove the withdrawal transaction 907 optimismPortal2.proveWithdrawalTransaction(_tx, _proposedGameIndex, proof, withdrawalProof); 908 (IDisputeGame _game,) = optimismPortal2.provenWithdrawals(withdrawalHash); 909 assertTrue(_game.rootClaim().raw() != bytes32(0)); 910 911 // Resolve the dispute game 912 game.resolveClaim(0); 913 game.resolve(); 914 915 // Warp past the finalization period 916 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 917 918 // Finalize the withdrawal transaction 919 vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); 920 optimismPortal2.finalizeWithdrawalTransaction(_tx); 921 assertTrue(optimismPortal2.finalizedWithdrawals(withdrawalHash)); 922 } 923 924 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal's dispute game has been blacklisted. 925 function test_finalizeWithdrawalTransaction_blacklisted_reverts() external { 926 vm.expectEmit(true, true, true, true); 927 emit WithdrawalProven(_withdrawalHash, alice, bob); 928 optimismPortal2.proveWithdrawalTransaction({ 929 _tx: _defaultTx, 930 _disputeGameIndex: _proposedGameIndex, 931 _outputRootProof: _outputRootProof, 932 _withdrawalProof: _withdrawalProof 933 }); 934 935 // Resolve the dispute game. 936 game.resolveClaim(0); 937 game.resolve(); 938 939 vm.prank(optimismPortal2.guardian()); 940 optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); 941 942 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 943 944 vm.expectRevert("OptimismPortal: dispute game has been blacklisted"); 945 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 946 } 947 948 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal's dispute game is still in the air 949 /// gap. 950 function test_finalizeWithdrawalTransaction_gameInAirGap_reverts() external { 951 vm.expectEmit(true, true, true, true); 952 emit WithdrawalProven(_withdrawalHash, alice, bob); 953 optimismPortal2.proveWithdrawalTransaction({ 954 _tx: _defaultTx, 955 _disputeGameIndex: _proposedGameIndex, 956 _outputRootProof: _outputRootProof, 957 _withdrawalProof: _withdrawalProof 958 }); 959 960 // Warp past the finalization period. 961 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 962 963 // Resolve the dispute game. 964 game.resolveClaim(0); 965 game.resolve(); 966 967 // Attempt to finalize the withdrawal directly after the game resolves. This should fail. 968 vm.expectRevert("OptimismPortal: output proposal in air-gap"); 969 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 970 971 // Finalize the withdrawal transaction. This should succeed. 972 vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); 973 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 974 assertTrue(optimismPortal2.finalizedWithdrawals(_withdrawalHash)); 975 } 976 977 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the respected game type has changed since the 978 /// withdrawal was proven. 979 function test_finalizeWithdrawalTransaction_respectedTypeChangedSinceProving_reverts() external { 980 vm.expectEmit(true, true, true, true); 981 emit WithdrawalProven(_withdrawalHash, alice, bob); 982 optimismPortal2.proveWithdrawalTransaction({ 983 _tx: _defaultTx, 984 _disputeGameIndex: _proposedGameIndex, 985 _outputRootProof: _outputRootProof, 986 _withdrawalProof: _withdrawalProof 987 }); 988 989 // Warp past the finalization period. 990 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 991 992 // Resolve the dispute game. 993 game.resolveClaim(0); 994 game.resolve(); 995 996 // Change the respected game type in the portal. 997 vm.prank(optimismPortal2.guardian()); 998 optimismPortal2.setRespectedGameType(GameType.wrap(0xFF)); 999 1000 vm.expectRevert("OptimismPortal: invalid game type"); 1001 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1002 } 1003 1004 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the respected game type was updated after the 1005 /// dispute game was created. 1006 function test_finalizeWithdrawalTransaction_gameOlderThanRespectedGameTypeUpdate_reverts() external { 1007 vm.expectEmit(true, true, true, true); 1008 emit WithdrawalProven(_withdrawalHash, alice, bob); 1009 optimismPortal2.proveWithdrawalTransaction({ 1010 _tx: _defaultTx, 1011 _disputeGameIndex: _proposedGameIndex, 1012 _outputRootProof: _outputRootProof, 1013 _withdrawalProof: _withdrawalProof 1014 }); 1015 1016 // Warp past the finalization period. 1017 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); 1018 1019 // Resolve the dispute game. 1020 game.resolveClaim(0); 1021 game.resolve(); 1022 1023 // Change the respected game type in the portal. 1024 vm.prank(optimismPortal2.guardian()); 1025 optimismPortal2.setRespectedGameType(GameType.wrap(0xFF)); 1026 1027 // Mock the game's type so that we pass the correct game type check. 1028 vm.mockCall(address(game), abi.encodeCall(game.gameType, ()), abi.encode(GameType.wrap(0xFF))); 1029 1030 vm.expectRevert("OptimismPortal: dispute game created before respected game type was updated"); 1031 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1032 } 1033 1034 /// @dev Tests an e2e prove -> finalize path, checking the edges of each delay for correctness. 1035 function test_finalizeWithdrawalTransaction_delayEdges_succeeds() external { 1036 // Prove the withdrawal transaction. 1037 vm.expectEmit(true, true, true, true); 1038 emit WithdrawalProven(_withdrawalHash, alice, bob); 1039 optimismPortal2.proveWithdrawalTransaction({ 1040 _tx: _defaultTx, 1041 _disputeGameIndex: _proposedGameIndex, 1042 _outputRootProof: _outputRootProof, 1043 _withdrawalProof: _withdrawalProof 1044 }); 1045 1046 // Attempt to finalize the withdrawal transaction 1 second before the proof has matured. This should fail. 1047 vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds()); 1048 vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); 1049 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1050 1051 // Warp 1 second in the future, past the proof maturity delay, and attempt to finalize the withdrawal. 1052 // This should also fail, since the dispute game has not resolved yet. 1053 vm.warp(block.timestamp + 1 seconds); 1054 vm.expectRevert("OptimismPortal: output proposal has not been finalized yet"); 1055 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1056 1057 // Finalize the dispute game and attempt to finalize the withdrawal again. This should also fail, since the 1058 // air gap dispute game delay has not elapsed. 1059 game.resolveClaim(0); 1060 game.resolve(); 1061 vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); 1062 vm.expectRevert("OptimismPortal: output proposal in air-gap"); 1063 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1064 1065 // Warp 1 second in the future, past the air gap dispute game delay, and attempt to finalize the withdrawal. 1066 // This should succeed. 1067 vm.warp(block.timestamp + 1 seconds); 1068 optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); 1069 assertTrue(optimismPortal2.finalizedWithdrawals(_withdrawalHash)); 1070 } 1071 } 1072 1073 contract OptimismPortal2_Upgradeable_Test is CommonTest { 1074 function setUp() public override { 1075 super.enableFaultProofs(); 1076 super.setUp(); 1077 } 1078 1079 /// @dev Tests that the proxy is initialized correctly. 1080 function test_params_initValuesOnProxy_succeeds() external { 1081 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal2.params(); 1082 ResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig(); 1083 1084 assertEq(prevBaseFee, rcfg.minimumBaseFee); 1085 assertEq(prevBoughtGas, 0); 1086 assertEq(prevBlockNum, block.number); 1087 } 1088 1089 /// @dev Tests that the proxy can be upgraded. 1090 function test_upgradeToAndCall_upgrading_succeeds() external { 1091 // Check an unused slot before upgrading. 1092 bytes32 slot21Before = vm.load(address(optimismPortal2), bytes32(uint256(21))); 1093 assertEq(bytes32(0), slot21Before); 1094 1095 NextImpl nextImpl = new NextImpl(); 1096 1097 vm.startPrank(EIP1967Helper.getAdmin(address(optimismPortal2))); 1098 // The value passed to the initialize must be larger than the last value 1099 // that initialize was called with. 1100 Proxy(payable(address(optimismPortal2))).upgradeToAndCall( 1101 address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2) 1102 ); 1103 assertEq(Proxy(payable(address(optimismPortal2))).implementation(), address(nextImpl)); 1104 1105 // Verify that the NextImpl contract initialized its values according as expected 1106 bytes32 slot21After = vm.load(address(optimismPortal2), bytes32(uint256(21))); 1107 bytes32 slot21Expected = NextImpl(address(optimismPortal2)).slot21Init(); 1108 assertEq(slot21Expected, slot21After); 1109 } 1110 } 1111 1112 /// @title OptimismPortal2_ResourceFuzz_Test 1113 /// @dev Test various values of the resource metering config to ensure that deposits cannot be 1114 /// broken by changing the config. 1115 contract OptimismPortal2_ResourceFuzz_Test is CommonTest { 1116 /// @dev The max gas limit observed throughout this test. Setting this too high can cause 1117 /// the test to take too long to run. 1118 uint256 constant MAX_GAS_LIMIT = 30_000_000; 1119 1120 function setUp() public override { 1121 super.enableFaultProofs(); 1122 super.setUp(); 1123 } 1124 1125 /// @dev Test that various values of the resource metering config will not break deposits. 1126 function testFuzz_systemConfigDeposit_succeeds( 1127 uint32 _maxResourceLimit, 1128 uint8 _elasticityMultiplier, 1129 uint8 _baseFeeMaxChangeDenominator, 1130 uint32 _minimumBaseFee, 1131 uint32 _systemTxMaxGas, 1132 uint128 _maximumBaseFee, 1133 uint64 _gasLimit, 1134 uint64 _prevBoughtGas, 1135 uint128 _prevBaseFee, 1136 uint8 _blockDiff 1137 ) 1138 external 1139 { 1140 // Get the set system gas limit 1141 uint64 gasLimit = systemConfig.gasLimit(); 1142 // Bound resource config 1143 _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8)); 1144 _gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit)); 1145 _prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei)); 1146 // Prevent values that would cause reverts 1147 vm.assume(gasLimit >= _gasLimit); 1148 vm.assume(_minimumBaseFee < _maximumBaseFee); 1149 vm.assume(_baseFeeMaxChangeDenominator > 1); 1150 vm.assume(uint256(_maxResourceLimit) + uint256(_systemTxMaxGas) <= gasLimit); 1151 vm.assume(_elasticityMultiplier > 0); 1152 vm.assume(((_maxResourceLimit / _elasticityMultiplier) * _elasticityMultiplier) == _maxResourceLimit); 1153 _prevBoughtGas = uint64(bound(_prevBoughtGas, 0, _maxResourceLimit - _gasLimit)); 1154 _blockDiff = uint8(bound(_blockDiff, 0, 3)); 1155 // Pick a pseudorandom block number 1156 vm.roll(uint256(keccak256(abi.encode(_blockDiff))) % uint256(type(uint16).max) + uint256(_blockDiff)); 1157 1158 // Create a resource config to mock the call to the system config with 1159 ResourceMetering.ResourceConfig memory rcfg = ResourceMetering.ResourceConfig({ 1160 maxResourceLimit: _maxResourceLimit, 1161 elasticityMultiplier: _elasticityMultiplier, 1162 baseFeeMaxChangeDenominator: _baseFeeMaxChangeDenominator, 1163 minimumBaseFee: _minimumBaseFee, 1164 systemTxMaxGas: _systemTxMaxGas, 1165 maximumBaseFee: _maximumBaseFee 1166 }); 1167 vm.mockCall( 1168 address(systemConfig), abi.encodeWithSelector(systemConfig.resourceConfig.selector), abi.encode(rcfg) 1169 ); 1170 1171 // Set the resource params 1172 uint256 _prevBlockNum = block.number - _blockDiff; 1173 vm.store( 1174 address(optimismPortal2), 1175 bytes32(uint256(1)), 1176 bytes32((_prevBlockNum << 192) | (uint256(_prevBoughtGas) << 128) | _prevBaseFee) 1177 ); 1178 // Ensure that the storage setting is correct 1179 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal2.params(); 1180 assertEq(prevBaseFee, _prevBaseFee); 1181 assertEq(prevBoughtGas, _prevBoughtGas); 1182 assertEq(prevBlockNum, _prevBlockNum); 1183 1184 // Do a deposit, should not revert 1185 optimismPortal2.depositTransaction{ gas: MAX_GAS_LIMIT }({ 1186 _to: address(0x20), 1187 _value: 0x40, 1188 _gasLimit: _gasLimit, 1189 _isCreation: false, 1190 _data: hex"" 1191 }); 1192 } 1193 }