github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/L1/OptimismPortal.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 { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; 21 import { SystemConfig } from "src/L1/SystemConfig.sol"; 22 import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; 23 import { OptimismPortal } from "src/L1/OptimismPortal.sol"; 24 25 contract OptimismPortal_Test is CommonTest { 26 address depositor; 27 28 /// @notice Marked virtual to be overridden in 29 /// test/kontrol/deployment/DeploymentSummary.t.sol 30 function setUp() public virtual override { 31 super.setUp(); 32 depositor = makeAddr("depositor"); 33 } 34 35 /// @dev Tests that the constructor sets the correct values. 36 /// @notice Marked virtual to be overridden in 37 /// test/kontrol/deployment/DeploymentSummary.t.sol 38 function test_constructor_succeeds() external virtual { 39 OptimismPortal opImpl = OptimismPortal(payable(deploy.mustGetAddress("OptimismPortal"))); 40 assertEq(address(opImpl.L2_ORACLE()), address(0)); 41 assertEq(address(opImpl.l2Oracle()), address(0)); 42 assertEq(address(opImpl.SYSTEM_CONFIG()), address(0)); 43 assertEq(address(opImpl.systemConfig()), address(0)); 44 assertEq(address(opImpl.superchainConfig()), address(0)); 45 assertEq(opImpl.l2Sender(), Constants.DEFAULT_L2_SENDER); 46 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = opImpl.params(); 47 assertEq(prevBaseFee, 1 gwei); 48 assertEq(prevBoughtGas, 0); 49 assertEq(prevBlockNum, uint64(block.number)); 50 } 51 52 /// @dev Tests that the initializer sets the correct values. 53 /// @notice Marked virtual to be overridden in 54 /// test/kontrol/deployment/DeploymentSummary.t.sol 55 function test_initialize_succeeds() external virtual { 56 address guardian = deploy.cfg().superchainConfigGuardian(); 57 assertEq(address(optimismPortal.L2_ORACLE()), address(l2OutputOracle)); 58 assertEq(address(optimismPortal.l2Oracle()), address(l2OutputOracle)); 59 assertEq(address(optimismPortal.SYSTEM_CONFIG()), address(systemConfig)); 60 assertEq(address(optimismPortal.systemConfig()), address(systemConfig)); 61 assertEq(optimismPortal.GUARDIAN(), guardian); 62 assertEq(optimismPortal.guardian(), guardian); 63 assertEq(address(optimismPortal.superchainConfig()), address(superchainConfig)); 64 assertEq(optimismPortal.l2Sender(), Constants.DEFAULT_L2_SENDER); 65 assertEq(optimismPortal.paused(), false); 66 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params(); 67 assertEq(prevBaseFee, 1 gwei); 68 assertEq(prevBoughtGas, 0); 69 assertEq(prevBlockNum, uint64(block.number)); 70 } 71 72 /// @dev Tests that `pause` successfully pauses 73 /// when called by the GUARDIAN. 74 function test_pause_succeeds() external { 75 address guardian = optimismPortal.GUARDIAN(); 76 77 assertEq(optimismPortal.paused(), false); 78 79 vm.expectEmit(address(superchainConfig)); 80 emit Paused("identifier"); 81 82 vm.prank(guardian); 83 superchainConfig.pause("identifier"); 84 85 assertEq(optimismPortal.paused(), true); 86 } 87 88 /// @dev Tests that `pause` reverts when called by a non-GUARDIAN. 89 function test_pause_onlyGuardian_reverts() external { 90 assertEq(optimismPortal.paused(), false); 91 92 assertTrue(optimismPortal.GUARDIAN() != alice); 93 vm.expectRevert("SuperchainConfig: only guardian can pause"); 94 vm.prank(alice); 95 superchainConfig.pause("identifier"); 96 97 assertEq(optimismPortal.paused(), false); 98 } 99 100 /// @dev Tests that `unpause` successfully unpauses 101 /// when called by the GUARDIAN. 102 function test_unpause_succeeds() external { 103 address guardian = optimismPortal.GUARDIAN(); 104 105 vm.prank(guardian); 106 superchainConfig.pause("identifier"); 107 assertEq(optimismPortal.paused(), true); 108 109 vm.expectEmit(address(superchainConfig)); 110 emit Unpaused(); 111 vm.prank(guardian); 112 superchainConfig.unpause(); 113 114 assertEq(optimismPortal.paused(), false); 115 } 116 117 /// @dev Tests that `unpause` reverts when called by a non-GUARDIAN. 118 function test_unpause_onlyGuardian_reverts() external { 119 address guardian = optimismPortal.GUARDIAN(); 120 121 vm.prank(guardian); 122 superchainConfig.pause("identifier"); 123 assertEq(optimismPortal.paused(), true); 124 125 assertTrue(optimismPortal.GUARDIAN() != alice); 126 vm.expectRevert("SuperchainConfig: only guardian can unpause"); 127 vm.prank(alice); 128 superchainConfig.unpause(); 129 130 assertEq(optimismPortal.paused(), true); 131 } 132 133 /// @dev Tests that `receive` successdully deposits ETH. 134 function testFuzz_receive_succeeds(uint256 _value) external { 135 vm.expectEmit(address(optimismPortal)); 136 emitTransactionDeposited({ 137 _from: alice, 138 _to: alice, 139 _value: _value, 140 _mint: _value, 141 _gasLimit: 100_000, 142 _isCreation: false, 143 _data: hex"" 144 }); 145 146 // give alice money and send as an eoa 147 vm.deal(alice, _value); 148 vm.prank(alice, alice); 149 (bool s,) = address(optimismPortal).call{ value: _value }(hex""); 150 151 assertTrue(s); 152 assertEq(address(optimismPortal).balance, _value); 153 } 154 155 /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero 156 /// for a contract creation deposit. 157 function test_depositTransaction_contractCreation_reverts() external { 158 // contract creation must have a target of address(0) 159 vm.expectRevert("OptimismPortal: must send to address(0) when creating a contract"); 160 optimismPortal.depositTransaction(address(1), 1, 0, true, hex""); 161 } 162 163 /// @dev Tests that `depositTransaction` reverts when the data is too large. 164 /// This places an upper bound on unsafe blocks sent over p2p. 165 function test_depositTransaction_largeData_reverts() external { 166 uint256 size = 120_001; 167 uint64 gasLimit = optimismPortal.minimumGasLimit(uint64(size)); 168 vm.expectRevert("OptimismPortal: data too large"); 169 optimismPortal.depositTransaction({ 170 _to: address(0), 171 _value: 0, 172 _gasLimit: gasLimit, 173 _isCreation: false, 174 _data: new bytes(size) 175 }); 176 } 177 178 /// @dev Tests that `depositTransaction` reverts when the gas limit is too small. 179 function test_depositTransaction_smallGasLimit_reverts() external { 180 vm.expectRevert("OptimismPortal: gas limit too small"); 181 optimismPortal.depositTransaction({ _to: address(1), _value: 0, _gasLimit: 0, _isCreation: false, _data: hex"" }); 182 } 183 184 /// @dev Tests that `depositTransaction` succeeds for small, 185 /// but sufficient, gas limits. 186 function testFuzz_depositTransaction_smallGasLimit_succeeds(bytes memory _data, bool _shouldFail) external { 187 uint64 gasLimit = optimismPortal.minimumGasLimit(uint64(_data.length)); 188 if (_shouldFail) { 189 gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1)); 190 vm.expectRevert("OptimismPortal: gas limit too small"); 191 } 192 193 optimismPortal.depositTransaction({ 194 _to: address(0x40), 195 _value: 0, 196 _gasLimit: gasLimit, 197 _isCreation: false, 198 _data: _data 199 }); 200 } 201 202 /// @dev Tests that `minimumGasLimit` succeeds for small calldata sizes. 203 /// The gas limit should be 21k for 0 calldata and increase linearly 204 /// for larger calldata sizes. 205 function test_minimumGasLimit_succeeds() external { 206 assertEq(optimismPortal.minimumGasLimit(0), 21_000); 207 assertTrue(optimismPortal.minimumGasLimit(2) > optimismPortal.minimumGasLimit(1)); 208 assertTrue(optimismPortal.minimumGasLimit(3) > optimismPortal.minimumGasLimit(2)); 209 } 210 211 /// @dev Tests that `depositTransaction` succeeds for an EOA. 212 function testFuzz_depositTransaction_eoa_succeeds( 213 address _to, 214 uint64 _gasLimit, 215 uint256 _value, 216 uint256 _mint, 217 bool _isCreation, 218 bytes memory _data 219 ) 220 external 221 { 222 _gasLimit = uint64( 223 bound( 224 _gasLimit, 225 optimismPortal.minimumGasLimit(uint64(_data.length)), 226 systemConfig.resourceConfig().maxResourceLimit 227 ) 228 ); 229 if (_isCreation) _to = address(0); 230 231 // EOA emulation 232 vm.expectEmit(address(optimismPortal)); 233 emitTransactionDeposited({ 234 _from: depositor, 235 _to: _to, 236 _value: _value, 237 _mint: _mint, 238 _gasLimit: _gasLimit, 239 _isCreation: _isCreation, 240 _data: _data 241 }); 242 243 vm.deal(depositor, _mint); 244 vm.prank(depositor, depositor); 245 optimismPortal.depositTransaction{ value: _mint }({ 246 _to: _to, 247 _value: _value, 248 _gasLimit: _gasLimit, 249 _isCreation: _isCreation, 250 _data: _data 251 }); 252 assertEq(address(optimismPortal).balance, _mint); 253 } 254 255 /// @dev Tests that `depositTransaction` succeeds for a contract. 256 function testFuzz_depositTransaction_contract_succeeds( 257 address _to, 258 uint64 _gasLimit, 259 uint256 _value, 260 uint256 _mint, 261 bool _isCreation, 262 bytes memory _data 263 ) 264 external 265 { 266 _gasLimit = uint64( 267 bound( 268 _gasLimit, 269 optimismPortal.minimumGasLimit(uint64(_data.length)), 270 systemConfig.resourceConfig().maxResourceLimit 271 ) 272 ); 273 if (_isCreation) _to = address(0); 274 275 vm.expectEmit(address(optimismPortal)); 276 emitTransactionDeposited({ 277 _from: AddressAliasHelper.applyL1ToL2Alias(address(this)), 278 _to: _to, 279 _value: _value, 280 _mint: _mint, 281 _gasLimit: _gasLimit, 282 _isCreation: _isCreation, 283 _data: _data 284 }); 285 286 vm.deal(address(this), _mint); 287 vm.prank(address(this)); 288 optimismPortal.depositTransaction{ value: _mint }({ 289 _to: _to, 290 _value: _value, 291 _gasLimit: _gasLimit, 292 _isCreation: _isCreation, 293 _data: _data 294 }); 295 assertEq(address(optimismPortal).balance, _mint); 296 } 297 298 /// @dev Tests that `isOutputFinalized` succeeds for an EOA depositing a tx with ETH and data. 299 /// @notice Marked virtual to be overridden in 300 /// test/kontrol/deployment/DeploymentSummary.t.sol 301 function test_simple_isOutputFinalized_succeeds() external virtual { 302 uint256 startingBlockNumber = deploy.cfg().l2OutputOracleStartingBlockNumber(); 303 304 uint256 ts = block.timestamp; 305 vm.mockCall( 306 address(optimismPortal.l2Oracle()), 307 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 308 abi.encode(Types.OutputProposal(bytes32(uint256(1)), uint128(ts), uint128(startingBlockNumber))) 309 ); 310 311 // warp to the finalization period 312 vm.warp(ts + l2OutputOracle.FINALIZATION_PERIOD_SECONDS()); 313 assertEq(optimismPortal.isOutputFinalized(0), false); 314 315 // warp past the finalization period 316 vm.warp(ts + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 317 assertEq(optimismPortal.isOutputFinalized(0), true); 318 } 319 320 /// @dev Tests `isOutputFinalized` for a finalized output. 321 /// @notice Marked virtual to be overridden in 322 /// test/kontrol/deployment/DeploymentSummary.t.sol 323 function test_isOutputFinalized_succeeds() external virtual { 324 uint256 checkpoint = l2OutputOracle.nextBlockNumber(); 325 uint256 nextOutputIndex = l2OutputOracle.nextOutputIndex(); 326 vm.roll(checkpoint); 327 vm.warp(l2OutputOracle.computeL2Timestamp(checkpoint) + 1); 328 vm.prank(l2OutputOracle.PROPOSER()); 329 l2OutputOracle.proposeL2Output(keccak256(abi.encode(2)), checkpoint, 0, 0); 330 331 // warp to the final second of the finalization period 332 uint256 finalizationHorizon = block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS(); 333 vm.warp(finalizationHorizon); 334 // The checkpointed block should not be finalized until 1 second from now. 335 assertEq(optimismPortal.isOutputFinalized(nextOutputIndex), false); 336 // Nor should a block after it 337 vm.expectRevert(stdError.indexOOBError); 338 assertEq(optimismPortal.isOutputFinalized(nextOutputIndex + 1), false); 339 // warp past the finalization period 340 vm.warp(finalizationHorizon + 1); 341 // It should now be finalized. 342 assertEq(optimismPortal.isOutputFinalized(nextOutputIndex), true); 343 // But not the block after it. 344 vm.expectRevert(stdError.indexOOBError); 345 assertEq(optimismPortal.isOutputFinalized(nextOutputIndex + 1), false); 346 } 347 } 348 349 contract OptimismPortal_FinalizeWithdrawal_Test is CommonTest { 350 // Reusable default values for a test withdrawal 351 Types.WithdrawalTransaction _defaultTx; 352 353 uint256 _proposedOutputIndex; 354 uint256 _proposedBlockNumber; 355 bytes32 _stateRoot; 356 bytes32 _storageRoot; 357 bytes32 _outputRoot; 358 bytes32 _withdrawalHash; 359 bytes[] _withdrawalProof; 360 Types.OutputRootProof internal _outputRootProof; 361 362 // Use a constructor to set the storage vars above, so as to minimize the number of ffi calls. 363 constructor() { 364 super.setUp(); 365 _defaultTx = Types.WithdrawalTransaction({ 366 nonce: 0, 367 sender: alice, 368 target: bob, 369 value: 100, 370 gasLimit: 100_000, 371 data: hex"" 372 }); 373 // Get withdrawal proof data we can use for testing. 374 (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) = 375 ffi.getProveWithdrawalTransactionInputs(_defaultTx); 376 377 // Setup a dummy output root proof for reuse. 378 _outputRootProof = Types.OutputRootProof({ 379 version: bytes32(uint256(0)), 380 stateRoot: _stateRoot, 381 messagePasserStorageRoot: _storageRoot, 382 latestBlockhash: bytes32(uint256(0)) 383 }); 384 _proposedBlockNumber = l2OutputOracle.nextBlockNumber(); 385 _proposedOutputIndex = l2OutputOracle.nextOutputIndex(); 386 } 387 388 /// @dev Setup the system for a ready-to-use state. 389 function setUp() public override { 390 // Configure the oracle to return the output root we've prepared. 391 vm.warp(l2OutputOracle.computeL2Timestamp(_proposedBlockNumber) + 1); 392 vm.prank(l2OutputOracle.PROPOSER()); 393 l2OutputOracle.proposeL2Output(_outputRoot, _proposedBlockNumber, 0, 0); 394 395 // Warp beyond the finalization period for the block we've proposed. 396 vm.warp( 397 l2OutputOracle.getL2Output(_proposedOutputIndex).timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() 398 + 1 399 ); 400 // Fund the portal so that we can withdraw ETH. 401 vm.deal(address(optimismPortal), 0xFFFFFFFF); 402 } 403 404 /// @dev Asserts that the reentrant call will revert. 405 function callPortalAndExpectRevert() external payable { 406 vm.expectRevert("OptimismPortal: can only trigger one withdrawal per transaction"); 407 // Arguments here don't matter, as the require check is the first thing that happens. 408 // We assume that this has already been proven. 409 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 410 // Assert that the withdrawal was not finalized. 411 assertFalse(optimismPortal.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx))); 412 } 413 414 /// @dev Tests that `proveWithdrawalTransaction` reverts when paused. 415 function test_proveWithdrawalTransaction_paused_reverts() external { 416 vm.prank(optimismPortal.GUARDIAN()); 417 superchainConfig.pause("identifier"); 418 419 vm.expectRevert("OptimismPortal: paused"); 420 optimismPortal.proveWithdrawalTransaction({ 421 _tx: _defaultTx, 422 _l2OutputIndex: _proposedOutputIndex, 423 _outputRootProof: _outputRootProof, 424 _withdrawalProof: _withdrawalProof 425 }); 426 } 427 428 /// @dev Tests that `proveWithdrawalTransaction` reverts when the target is the portal contract. 429 function test_proveWithdrawalTransaction_onSelfCall_reverts() external { 430 _defaultTx.target = address(optimismPortal); 431 vm.expectRevert("OptimismPortal: you cannot send messages to the portal contract"); 432 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 433 } 434 435 /// @dev Tests that `proveWithdrawalTransaction` reverts when 436 /// the outputRootProof does not match the output root 437 function test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() external { 438 // Modify the version to invalidate the withdrawal proof. 439 _outputRootProof.version = bytes32(uint256(1)); 440 vm.expectRevert("OptimismPortal: invalid output root proof"); 441 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 442 } 443 444 /// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal is missing. 445 function test_proveWithdrawalTransaction_onInvalidWithdrawalProof_reverts() external { 446 // modify the default test values to invalidate the proof. 447 _defaultTx.data = hex"abcd"; 448 vm.expectRevert("MerkleTrie: path remainder must share all nibbles with key"); 449 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 450 } 451 452 /// @dev Tests that `proveWithdrawalTransaction` reverts when the withdrawal has already 453 /// been proven. 454 function test_proveWithdrawalTransaction_replayProve_reverts() external { 455 vm.expectEmit(true, true, true, true); 456 emit WithdrawalProven(_withdrawalHash, alice, bob); 457 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 458 459 vm.expectRevert("OptimismPortal: withdrawal hash has already been proven"); 460 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 461 } 462 463 /// @dev Tests that `proveWithdrawalTransaction` succeeds when the withdrawal has already 464 /// been proven and the output root has changed and the l2BlockNumber stays the same. 465 function test_proveWithdrawalTransaction_replayProveChangedOutputRoot_succeeds() external { 466 vm.expectEmit(true, true, true, true); 467 emit WithdrawalProven(_withdrawalHash, alice, bob); 468 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 469 470 // Compute the storage slot of the outputRoot corresponding to the `withdrawalHash` 471 // inside of the `provenWithdrawal`s mapping. 472 bytes32 slot; 473 assembly { 474 mstore(0x00, sload(_withdrawalHash.slot)) 475 mstore(0x20, 52) // 52 is the slot of the `provenWithdrawals` mapping in the OptimismPortal 476 slot := keccak256(0x00, 0x40) 477 } 478 479 // Store a different output root within the `provenWithdrawals` mapping without 480 // touching the l2BlockNumber or timestamp. 481 vm.store(address(optimismPortal), slot, bytes32(0)); 482 483 // Warp ahead 1 second 484 vm.warp(block.timestamp + 1); 485 486 // Even though we have already proven this withdrawalHash, we should be allowed to re-submit 487 // our proof with a changed outputRoot 488 vm.expectEmit(true, true, true, true); 489 emit WithdrawalProven(_withdrawalHash, alice, bob); 490 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 491 492 // Ensure that the withdrawal was updated within the mapping 493 (, uint128 timestamp,) = optimismPortal.provenWithdrawals(_withdrawalHash); 494 assertEq(timestamp, block.timestamp); 495 } 496 497 /// @dev Tests that `proveWithdrawalTransaction` succeeds when the withdrawal has already 498 /// been proven and the output root, output index, and l2BlockNumber have changed. 499 function test_proveWithdrawalTransaction_replayProveChangedOutputRootAndOutputIndex_succeeds() external { 500 vm.expectEmit(true, true, true, true); 501 emit WithdrawalProven(_withdrawalHash, alice, bob); 502 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 503 504 // Compute the storage slot of the outputRoot corresponding to the `withdrawalHash` 505 // inside of the `provenWithdrawal`s mapping. 506 bytes32 slot; 507 assembly { 508 mstore(0x00, sload(_withdrawalHash.slot)) 509 mstore(0x20, 52) // 52 is the slot of the `provenWithdrawals` mapping in OptimismPortal 510 slot := keccak256(0x00, 0x40) 511 } 512 513 // Store a dummy output root within the `provenWithdrawals` mapping without touching the 514 // l2BlockNumber or timestamp. 515 vm.store(address(optimismPortal), slot, bytes32(0)); 516 517 // Fetch the output proposal at `_proposedOutputIndex` from the L2OutputOracle 518 Types.OutputProposal memory proposal = optimismPortal.l2Oracle().getL2Output(_proposedOutputIndex); 519 520 // Propose the same output root again, creating the same output at a different index + l2BlockNumber. 521 vm.startPrank(optimismPortal.l2Oracle().PROPOSER()); 522 optimismPortal.l2Oracle().proposeL2Output( 523 proposal.outputRoot, optimismPortal.l2Oracle().nextBlockNumber(), blockhash(block.number), block.number 524 ); 525 vm.stopPrank(); 526 527 // Warp ahead 1 second 528 vm.warp(block.timestamp + 1); 529 530 // Even though we have already proven this withdrawalHash, we should be allowed to re-submit 531 // our proof with a changed outputRoot + a different output index 532 vm.expectEmit(true, true, true, true); 533 emit WithdrawalProven(_withdrawalHash, alice, bob); 534 optimismPortal.proveWithdrawalTransaction( 535 _defaultTx, _proposedOutputIndex + 1, _outputRootProof, _withdrawalProof 536 ); 537 538 // Ensure that the withdrawal was updated within the mapping 539 (, uint128 timestamp,) = optimismPortal.provenWithdrawals(_withdrawalHash); 540 assertEq(timestamp, block.timestamp); 541 } 542 543 /// @dev Tests that `proveWithdrawalTransaction` succeeds. 544 function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external { 545 vm.expectEmit(true, true, true, true); 546 emit WithdrawalProven(_withdrawalHash, alice, bob); 547 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 548 } 549 550 /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. 551 function test_finalizeWithdrawalTransaction_provenWithdrawalHash_succeeds() external { 552 uint256 bobBalanceBefore = address(bob).balance; 553 554 vm.expectEmit(true, true, true, true); 555 emit WithdrawalProven(_withdrawalHash, alice, bob); 556 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 557 558 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 559 vm.expectEmit(true, true, false, true); 560 emit WithdrawalFinalized(_withdrawalHash, true); 561 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 562 563 assert(address(bob).balance == bobBalanceBefore + 100); 564 } 565 566 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the contract is paused. 567 function test_finalizeWithdrawalTransaction_paused_reverts() external { 568 vm.prank(optimismPortal.GUARDIAN()); 569 superchainConfig.pause("identifier"); 570 571 vm.expectRevert("OptimismPortal: paused"); 572 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 573 } 574 575 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been 576 function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external { 577 uint256 bobBalanceBefore = address(bob).balance; 578 579 vm.expectRevert("OptimismPortal: withdrawal has not been proven yet"); 580 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 581 582 assert(address(bob).balance == bobBalanceBefore); 583 } 584 585 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has not been 586 /// proven long enough ago. 587 function test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() external { 588 uint256 bobBalanceBefore = address(bob).balance; 589 590 vm.expectEmit(true, true, true, true); 591 emit WithdrawalProven(_withdrawalHash, alice, bob); 592 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 593 594 // Mock a call where the resulting output root is anything but the original output root. In 595 // this case we just use bytes32(uint256(1)). 596 vm.mockCall( 597 address(optimismPortal.l2Oracle()), 598 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 599 abi.encode(bytes32(uint256(1)), _proposedBlockNumber) 600 ); 601 602 vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed"); 603 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 604 605 assert(address(bob).balance == bobBalanceBefore); 606 } 607 608 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the provenWithdrawal's timestamp 609 /// is less than the L2 output oracle's starting timestamp. 610 function test_finalizeWithdrawalTransaction_timestampLessThanL2OracleStart_reverts() external { 611 uint256 bobBalanceBefore = address(bob).balance; 612 613 // Prove our withdrawal 614 vm.expectEmit(true, true, true, true); 615 emit WithdrawalProven(_withdrawalHash, alice, bob); 616 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 617 618 // Warp to after the finalization period 619 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 620 621 // Mock a startingTimestamp change on the L2 Oracle 622 vm.mockCall( 623 address(optimismPortal.l2Oracle()), 624 abi.encodeWithSignature("startingTimestamp()"), 625 abi.encode(block.timestamp + 1) 626 ); 627 628 // Attempt to finalize the withdrawal 629 vm.expectRevert("OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"); 630 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 631 632 // Ensure that bob's balance has remained the same 633 assertEq(bobBalanceBefore, address(bob).balance); 634 } 635 636 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the output root proven is not the 637 /// same as the output root at the time of finalization. 638 function test_finalizeWithdrawalTransaction_ifOutputRootChanges_reverts() external { 639 uint256 bobBalanceBefore = address(bob).balance; 640 641 // Prove our withdrawal 642 vm.expectEmit(true, true, true, true); 643 emit WithdrawalProven(_withdrawalHash, alice, bob); 644 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 645 646 // Warp to after the finalization period 647 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 648 649 // Mock an outputRoot change on the output proposal before attempting 650 // to finalize the withdrawal. 651 vm.mockCall( 652 address(optimismPortal.l2Oracle()), 653 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 654 abi.encode( 655 Types.OutputProposal(bytes32(uint256(0)), uint128(block.timestamp), uint128(_proposedBlockNumber)) 656 ) 657 ); 658 659 // Attempt to finalize the withdrawal 660 vm.expectRevert("OptimismPortal: output root proven is not the same as current output root"); 661 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 662 663 // Ensure that bob's balance has remained the same 664 assertEq(bobBalanceBefore, address(bob).balance); 665 } 666 667 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the output proposal's timestamp 668 /// has not passed the finalization period. 669 function test_finalizeWithdrawalTransaction_ifOutputTimestampIsNotFinalized_reverts() external { 670 uint256 bobBalanceBefore = address(bob).balance; 671 672 // Prove our withdrawal 673 vm.expectEmit(true, true, true, true); 674 emit WithdrawalProven(_withdrawalHash, alice, bob); 675 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 676 677 // Warp to after the finalization period 678 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 679 680 // Mock a timestamp change on the output proposal that has not passed the 681 // finalization period. 682 vm.mockCall( 683 address(optimismPortal.l2Oracle()), 684 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 685 abi.encode(Types.OutputProposal(_outputRoot, uint128(block.timestamp + 1), uint128(_proposedBlockNumber))) 686 ); 687 688 // Attempt to finalize the withdrawal 689 vm.expectRevert("OptimismPortal: output proposal finalization period has not elapsed"); 690 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 691 692 // Ensure that bob's balance has remained the same 693 assertEq(bobBalanceBefore, address(bob).balance); 694 } 695 696 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the target reverts. 697 function test_finalizeWithdrawalTransaction_targetFails_fails() external { 698 uint256 bobBalanceBefore = address(bob).balance; 699 vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. 700 701 vm.expectEmit(true, true, true, true); 702 emit WithdrawalProven(_withdrawalHash, alice, bob); 703 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 704 705 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 706 vm.expectEmit(true, true, true, true); 707 emit WithdrawalFinalized(_withdrawalHash, false); 708 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 709 710 assert(address(bob).balance == bobBalanceBefore); 711 } 712 713 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the finalization period 714 /// has not yet passed. 715 function test_finalizeWithdrawalTransaction_onRecentWithdrawal_reverts() external { 716 // Setup the Oracle to return an output with a recent timestamp 717 uint256 recentTimestamp = block.timestamp - 1; 718 vm.mockCall( 719 address(optimismPortal.l2Oracle()), 720 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 721 abi.encode(Types.OutputProposal(_outputRoot, uint128(recentTimestamp), uint128(_proposedBlockNumber))) 722 ); 723 724 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 725 726 vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed"); 727 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 728 } 729 730 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has already been 731 /// finalized. 732 function test_finalizeWithdrawalTransaction_onReplay_reverts() external { 733 vm.expectEmit(true, true, true, true); 734 emit WithdrawalProven(_withdrawalHash, alice, bob); 735 optimismPortal.proveWithdrawalTransaction(_defaultTx, _proposedOutputIndex, _outputRootProof, _withdrawalProof); 736 737 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 738 vm.expectEmit(true, true, true, true); 739 emit WithdrawalFinalized(_withdrawalHash, true); 740 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 741 742 vm.expectRevert("OptimismPortal: withdrawal has already been finalized"); 743 optimismPortal.finalizeWithdrawalTransaction(_defaultTx); 744 } 745 746 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal transaction 747 /// does not have enough gas to execute. 748 function test_finalizeWithdrawalTransaction_onInsufficientGas_reverts() external { 749 // This number was identified through trial and error. 750 uint256 gasLimit = 150_000; 751 Types.WithdrawalTransaction memory insufficientGasTx = Types.WithdrawalTransaction({ 752 nonce: 0, 753 sender: alice, 754 target: bob, 755 value: 100, 756 gasLimit: gasLimit, 757 data: hex"" 758 }); 759 760 // Get updated proof inputs. 761 (bytes32 stateRoot, bytes32 storageRoot,,, bytes[] memory withdrawalProof) = 762 ffi.getProveWithdrawalTransactionInputs(insufficientGasTx); 763 Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({ 764 version: bytes32(0), 765 stateRoot: stateRoot, 766 messagePasserStorageRoot: storageRoot, 767 latestBlockhash: bytes32(0) 768 }); 769 770 vm.mockCall( 771 address(optimismPortal.l2Oracle()), 772 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 773 abi.encode( 774 Types.OutputProposal( 775 Hashing.hashOutputRootProof(outputRootProof), 776 uint128(block.timestamp), 777 uint128(_proposedBlockNumber) 778 ) 779 ) 780 ); 781 782 optimismPortal.proveWithdrawalTransaction( 783 insufficientGasTx, _proposedOutputIndex, outputRootProof, withdrawalProof 784 ); 785 786 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 787 vm.expectRevert("SafeCall: Not enough gas"); 788 optimismPortal.finalizeWithdrawalTransaction{ gas: gasLimit }(insufficientGasTx); 789 } 790 791 /// @dev Tests that `finalizeWithdrawalTransaction` reverts if a sub-call attempts to finalize 792 /// another withdrawal. 793 function test_finalizeWithdrawalTransaction_onReentrancy_reverts() external { 794 uint256 bobBalanceBefore = address(bob).balance; 795 796 // Copy and modify the default test values to attempt a reentrant call by first calling to 797 // this contract's callPortalAndExpectRevert() function above. 798 Types.WithdrawalTransaction memory _testTx = _defaultTx; 799 _testTx.target = address(this); 800 _testTx.data = abi.encodeWithSelector(this.callPortalAndExpectRevert.selector); 801 802 // Get modified proof inputs. 803 ( 804 bytes32 stateRoot, 805 bytes32 storageRoot, 806 bytes32 outputRoot, 807 bytes32 withdrawalHash, 808 bytes[] memory withdrawalProof 809 ) = ffi.getProveWithdrawalTransactionInputs(_testTx); 810 Types.OutputRootProof memory outputRootProof = Types.OutputRootProof({ 811 version: bytes32(0), 812 stateRoot: stateRoot, 813 messagePasserStorageRoot: storageRoot, 814 latestBlockhash: bytes32(0) 815 }); 816 817 // Setup the Oracle to return the outputRoot we want as well as a finalized timestamp. 818 uint256 finalizedTimestamp = block.timestamp - l2OutputOracle.FINALIZATION_PERIOD_SECONDS() - 1; 819 vm.mockCall( 820 address(optimismPortal.l2Oracle()), 821 abi.encodeWithSelector(L2OutputOracle.getL2Output.selector), 822 abi.encode(Types.OutputProposal(outputRoot, uint128(finalizedTimestamp), uint128(_proposedBlockNumber))) 823 ); 824 825 vm.expectEmit(true, true, true, true); 826 emit WithdrawalProven(withdrawalHash, alice, address(this)); 827 optimismPortal.proveWithdrawalTransaction(_testTx, _proposedBlockNumber, outputRootProof, withdrawalProof); 828 829 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 830 vm.expectCall(address(this), _testTx.data); 831 vm.expectEmit(true, true, true, true); 832 emit WithdrawalFinalized(withdrawalHash, true); 833 optimismPortal.finalizeWithdrawalTransaction(_testTx); 834 835 // Ensure that bob's balance was not changed by the reentrant call. 836 assert(address(bob).balance == bobBalanceBefore); 837 } 838 839 /// @dev Tests that `finalizeWithdrawalTransaction` succeeds. 840 function testDiff_finalizeWithdrawalTransaction_succeeds( 841 address _sender, 842 address _target, 843 uint256 _value, 844 uint256 _gasLimit, 845 bytes memory _data 846 ) 847 external 848 { 849 vm.assume( 850 _target != address(optimismPortal) // Cannot call the optimism portal or a contract 851 && _target.code.length == 0 // No accounts with code 852 && _target != CONSOLE // The console has no code but behaves like a contract 853 && uint160(_target) > 9 // No precompiles (or zero address) 854 ); 855 856 // Total ETH supply is currently about 120M ETH. 857 uint256 value = bound(_value, 0, 200_000_000 ether); 858 vm.deal(address(optimismPortal), value); 859 860 uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); 861 uint256 nonce = l2ToL1MessagePasser.messageNonce(); 862 863 // Get a withdrawal transaction and mock proof from the differential testing script. 864 Types.WithdrawalTransaction memory _tx = Types.WithdrawalTransaction({ 865 nonce: nonce, 866 sender: _sender, 867 target: _target, 868 value: value, 869 gasLimit: gasLimit, 870 data: _data 871 }); 872 ( 873 bytes32 stateRoot, 874 bytes32 storageRoot, 875 bytes32 outputRoot, 876 bytes32 withdrawalHash, 877 bytes[] memory withdrawalProof 878 ) = ffi.getProveWithdrawalTransactionInputs(_tx); 879 880 // Create the output root proof 881 Types.OutputRootProof memory proof = Types.OutputRootProof({ 882 version: bytes32(uint256(0)), 883 stateRoot: stateRoot, 884 messagePasserStorageRoot: storageRoot, 885 latestBlockhash: bytes32(uint256(0)) 886 }); 887 888 // Ensure the values returned from ffi are correct 889 assertEq(outputRoot, Hashing.hashOutputRootProof(proof)); 890 assertEq(withdrawalHash, Hashing.hashWithdrawal(_tx)); 891 892 // Setup the Oracle to return the outputRoot 893 vm.mockCall( 894 address(l2OutputOracle), 895 abi.encodeWithSelector(l2OutputOracle.getL2Output.selector), 896 abi.encode(outputRoot, block.timestamp, 100) 897 ); 898 899 // Prove the withdrawal transaction 900 optimismPortal.proveWithdrawalTransaction( 901 _tx, 902 100, // l2BlockNumber 903 proof, 904 withdrawalProof 905 ); 906 (bytes32 _root,,) = optimismPortal.provenWithdrawals(withdrawalHash); 907 assertTrue(_root != bytes32(0)); 908 909 // Warp past the finalization period 910 vm.warp(block.timestamp + l2OutputOracle.FINALIZATION_PERIOD_SECONDS() + 1); 911 912 // Finalize the withdrawal transaction 913 vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); 914 optimismPortal.finalizeWithdrawalTransaction(_tx); 915 assertTrue(optimismPortal.finalizedWithdrawals(withdrawalHash)); 916 } 917 } 918 919 contract OptimismPortalUpgradeable_Test is CommonTest { 920 /// @dev Tests that the proxy is initialized correctly. 921 function test_params_initValuesOnProxy_succeeds() external { 922 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params(); 923 ResourceMetering.ResourceConfig memory rcfg = systemConfig.resourceConfig(); 924 925 assertEq(prevBaseFee, rcfg.minimumBaseFee); 926 assertEq(prevBoughtGas, 0); 927 assertEq(prevBlockNum, block.number); 928 } 929 930 /// @dev Tests that the proxy can be upgraded. 931 function test_upgradeToAndCall_upgrading_succeeds() external { 932 // Check an unused slot before upgrading. 933 bytes32 slot21Before = vm.load(address(optimismPortal), bytes32(uint256(21))); 934 assertEq(bytes32(0), slot21Before); 935 936 NextImpl nextImpl = new NextImpl(); 937 938 vm.startPrank(EIP1967Helper.getAdmin(address(optimismPortal))); 939 // The value passed to the initialize must be larger than the last value 940 // that initialize was called with. 941 Proxy(payable(address(optimismPortal))).upgradeToAndCall( 942 address(nextImpl), abi.encodeWithSelector(NextImpl.initialize.selector, 2) 943 ); 944 assertEq(Proxy(payable(address(optimismPortal))).implementation(), address(nextImpl)); 945 946 // Verify that the NextImpl contract initialized its values according as expected 947 bytes32 slot21After = vm.load(address(optimismPortal), bytes32(uint256(21))); 948 bytes32 slot21Expected = NextImpl(address(optimismPortal)).slot21Init(); 949 assertEq(slot21Expected, slot21After); 950 } 951 } 952 953 /// @title OptimismPortalResourceFuzz_Test 954 /// @dev Test various values of the resource metering config to ensure that deposits cannot be 955 /// broken by changing the config. 956 contract OptimismPortalResourceFuzz_Test is CommonTest { 957 /// @dev The max gas limit observed throughout this test. Setting this too high can cause 958 /// the test to take too long to run. 959 uint256 constant MAX_GAS_LIMIT = 30_000_000; 960 961 /// @dev Test that various values of the resource metering config will not break deposits. 962 function testFuzz_systemConfigDeposit_succeeds( 963 uint32 _maxResourceLimit, 964 uint8 _elasticityMultiplier, 965 uint8 _baseFeeMaxChangeDenominator, 966 uint32 _minimumBaseFee, 967 uint32 _systemTxMaxGas, 968 uint128 _maximumBaseFee, 969 uint64 _gasLimit, 970 uint64 _prevBoughtGas, 971 uint128 _prevBaseFee, 972 uint8 _blockDiff 973 ) 974 external 975 { 976 // Get the set system gas limit 977 uint64 gasLimit = systemConfig.gasLimit(); 978 // Bound resource config 979 _maxResourceLimit = uint32(bound(_maxResourceLimit, 21000, MAX_GAS_LIMIT / 8)); 980 _gasLimit = uint64(bound(_gasLimit, 21000, _maxResourceLimit)); 981 _prevBaseFee = uint128(bound(_prevBaseFee, 0, 3 gwei)); 982 // Prevent values that would cause reverts 983 vm.assume(gasLimit >= _gasLimit); 984 vm.assume(_minimumBaseFee < _maximumBaseFee); 985 vm.assume(_baseFeeMaxChangeDenominator > 1); 986 vm.assume(uint256(_maxResourceLimit) + uint256(_systemTxMaxGas) <= gasLimit); 987 vm.assume(_elasticityMultiplier > 0); 988 vm.assume(((_maxResourceLimit / _elasticityMultiplier) * _elasticityMultiplier) == _maxResourceLimit); 989 _prevBoughtGas = uint64(bound(_prevBoughtGas, 0, _maxResourceLimit - _gasLimit)); 990 _blockDiff = uint8(bound(_blockDiff, 0, 3)); 991 // Pick a pseudorandom block number 992 vm.roll(uint256(keccak256(abi.encode(_blockDiff))) % uint256(type(uint16).max) + uint256(_blockDiff)); 993 994 // Create a resource config to mock the call to the system config with 995 ResourceMetering.ResourceConfig memory rcfg = ResourceMetering.ResourceConfig({ 996 maxResourceLimit: _maxResourceLimit, 997 elasticityMultiplier: _elasticityMultiplier, 998 baseFeeMaxChangeDenominator: _baseFeeMaxChangeDenominator, 999 minimumBaseFee: _minimumBaseFee, 1000 systemTxMaxGas: _systemTxMaxGas, 1001 maximumBaseFee: _maximumBaseFee 1002 }); 1003 vm.mockCall( 1004 address(systemConfig), abi.encodeWithSelector(systemConfig.resourceConfig.selector), abi.encode(rcfg) 1005 ); 1006 1007 // Set the resource params 1008 uint256 _prevBlockNum = block.number - _blockDiff; 1009 vm.store( 1010 address(optimismPortal), 1011 bytes32(uint256(1)), 1012 bytes32((_prevBlockNum << 192) | (uint256(_prevBoughtGas) << 128) | _prevBaseFee) 1013 ); 1014 // Ensure that the storage setting is correct 1015 (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params(); 1016 assertEq(prevBaseFee, _prevBaseFee); 1017 assertEq(prevBoughtGas, _prevBoughtGas); 1018 assertEq(prevBlockNum, _prevBlockNum); 1019 1020 // Do a deposit, should not revert 1021 optimismPortal.depositTransaction{ gas: MAX_GAS_LIMIT }({ 1022 _to: address(0x20), 1023 _value: 0x40, 1024 _gasLimit: _gasLimit, 1025 _isCreation: false, 1026 _data: hex"" 1027 }); 1028 } 1029 }