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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { StdUtils } from "forge-std/Test.sol";
     5  import { Vm } from "forge-std/Vm.sol";
     6  
     7  import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
     8  import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
     9  import { SystemConfig } from "src/L1/SystemConfig.sol";
    10  import { ResourceMetering } from "src/L1/ResourceMetering.sol";
    11  import { Constants } from "src/libraries/Constants.sol";
    12  
    13  import { CommonTest } from "test/setup/CommonTest.sol";
    14  import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
    15  import { Types } from "src/libraries/Types.sol";
    16  
    17  import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol";
    18  import "src/libraries/DisputeTypes.sol";
    19  
    20  contract OptimismPortal2_Depositor is StdUtils, ResourceMetering {
    21      Vm internal vm;
    22      OptimismPortal2 internal portal;
    23      bool public failedToComplete;
    24  
    25      constructor(Vm _vm, OptimismPortal2 _portal) {
    26          vm = _vm;
    27          portal = _portal;
    28          initialize();
    29      }
    30  
    31      function initialize() internal initializer {
    32          __ResourceMetering_init();
    33      }
    34  
    35      function resourceConfig() public pure returns (ResourceMetering.ResourceConfig memory) {
    36          return _resourceConfig();
    37      }
    38  
    39      function _resourceConfig() internal pure override returns (ResourceMetering.ResourceConfig memory) {
    40          ResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG();
    41          return rcfg;
    42      }
    43  
    44      // A test intended to identify any unexpected halting conditions
    45      function depositTransactionCompletes(
    46          address _to,
    47          uint256 _value,
    48          uint64 _gasLimit,
    49          bool _isCreation,
    50          bytes memory _data
    51      )
    52          public
    53          payable
    54      {
    55          vm.assume((!_isCreation || _to == address(0)) && _data.length <= 120_000);
    56  
    57          uint256 preDepositvalue = bound(_value, 0, type(uint128).max);
    58          // Give the depositor some ether
    59          vm.deal(address(this), preDepositvalue);
    60          // cache the contract's eth balance
    61          uint256 preDepositBalance = address(this).balance;
    62          uint256 value = bound(preDepositvalue, 0, preDepositBalance);
    63  
    64          (, uint64 cachedPrevBoughtGas,) = ResourceMetering(address(portal)).params();
    65          ResourceMetering.ResourceConfig memory rcfg = resourceConfig();
    66          uint256 maxResourceLimit = uint64(rcfg.maxResourceLimit);
    67          uint64 gasLimit = uint64(
    68              bound(_gasLimit, portal.minimumGasLimit(uint64(_data.length)), maxResourceLimit - cachedPrevBoughtGas)
    69          );
    70  
    71          try portal.depositTransaction{ value: value }(_to, value, gasLimit, _isCreation, _data) {
    72              // Do nothing; Call succeeded
    73          } catch {
    74              failedToComplete = true;
    75          }
    76      }
    77  }
    78  
    79  contract OptimismPortal2_Invariant_Harness is CommonTest {
    80      // Reusable default values for a test withdrawal
    81      Types.WithdrawalTransaction _defaultTx;
    82  
    83      uint256 _proposedGameIndex;
    84      uint256 _proposedBlockNumber;
    85      bytes32 _stateRoot;
    86      bytes32 _storageRoot;
    87      bytes32 _outputRoot;
    88      bytes32 _withdrawalHash;
    89      bytes[] _withdrawalProof;
    90      Types.OutputRootProof internal _outputRootProof;
    91  
    92      function setUp() public virtual override {
    93          super.enableFaultProofs();
    94          super.setUp();
    95  
    96          _defaultTx = Types.WithdrawalTransaction({
    97              nonce: 0,
    98              sender: alice,
    99              target: bob,
   100              value: 100,
   101              gasLimit: 100_000,
   102              data: hex""
   103          });
   104          // Get withdrawal proof data we can use for testing.
   105          (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) =
   106              ffi.getProveWithdrawalTransactionInputs(_defaultTx);
   107  
   108          // Setup a dummy output root proof for reuse.
   109          _outputRootProof = Types.OutputRootProof({
   110              version: bytes32(uint256(0)),
   111              stateRoot: _stateRoot,
   112              messagePasserStorageRoot: _storageRoot,
   113              latestBlockhash: bytes32(uint256(0))
   114          });
   115  
   116          // Create a dispute game with the output root we've proposed.
   117          _proposedBlockNumber = 0xFF;
   118          FaultDisputeGame game = FaultDisputeGame(
   119              payable(
   120                  address(
   121                      disputeGameFactory.create(
   122                          optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber)
   123                      )
   124                  )
   125              )
   126          );
   127          _proposedGameIndex = disputeGameFactory.gameCount() - 1;
   128  
   129          // Warp beyond the finalization period for the dispute game and resolve it.
   130          vm.warp(block.timestamp + game.gameDuration().raw() + 1 seconds);
   131          game.resolveClaim(0);
   132          game.resolve();
   133  
   134          // Fund the portal so that we can withdraw ETH.
   135          vm.deal(address(optimismPortal2), 0xFFFFFFFF);
   136      }
   137  }
   138  
   139  contract OptimismPortal2_Deposit_Invariant is CommonTest {
   140      OptimismPortal2_Depositor internal actor;
   141  
   142      function setUp() public override {
   143          super.setUp();
   144          // Create a deposit actor.
   145          actor = new OptimismPortal2_Depositor(vm, optimismPortal2);
   146  
   147          targetContract(address(actor));
   148  
   149          bytes4[] memory selectors = new bytes4[](1);
   150          selectors[0] = actor.depositTransactionCompletes.selector;
   151          FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
   152          targetSelector(selector);
   153      }
   154  
   155      /// @custom:invariant Deposits of any value should always succeed unless
   156      ///                   `_to` = `address(0)` or `_isCreation` = `true`.
   157      ///
   158      ///                   All deposits, barring creation transactions and transactions
   159      ///                   sent to `address(0)`, should always succeed.
   160      function invariant_deposit_completes() external {
   161          assertEq(actor.failedToComplete(), false);
   162      }
   163  }
   164  
   165  contract OptimismPortal2_CannotTimeTravel is OptimismPortal2_Invariant_Harness {
   166      function setUp() public override {
   167          super.setUp();
   168  
   169          // Prove the withdrawal transaction
   170          optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof);
   171  
   172          // Set the target contract to the portal proxy
   173          targetContract(address(optimismPortal2));
   174          // Exclude the proxy admin from the senders so that the proxy cannot be upgraded
   175          excludeSender(EIP1967Helper.getAdmin(address(optimismPortal2)));
   176      }
   177  
   178      /// @custom:invariant `finalizeWithdrawalTransaction` should revert if the proof maturity period has not elapsed.
   179      ///
   180      ///                   A withdrawal that has been proven should not be able to be finalized
   181      ///                   until after the proof maturity period has elapsed.
   182      function invariant_cannotFinalizeBeforePeriodHasPassed() external {
   183          vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet");
   184          optimismPortal2.finalizeWithdrawalTransaction(_defaultTx);
   185      }
   186  }
   187  
   188  contract OptimismPortal2_CannotFinalizeTwice is OptimismPortal2_Invariant_Harness {
   189      function setUp() public override {
   190          super.setUp();
   191  
   192          // Prove the withdrawal transaction
   193          optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof);
   194  
   195          // Warp past the proof maturity period.
   196          vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1);
   197  
   198          // Finalize the withdrawal transaction.
   199          optimismPortal2.finalizeWithdrawalTransaction(_defaultTx);
   200  
   201          // Set the target contract to the portal proxy
   202          targetContract(address(optimismPortal2));
   203          // Exclude the proxy admin from the senders so that the proxy cannot be upgraded
   204          excludeSender(EIP1967Helper.getAdmin(address(optimismPortal2)));
   205      }
   206  
   207      /// @custom:invariant `finalizeWithdrawalTransaction` should revert if the withdrawal has already been finalized.
   208      ///
   209      ///                   Ensures that there is no chain of calls that can be made that allows a withdrawal to be
   210      ///                   finalized twice.
   211      function invariant_cannotFinalizeTwice() external {
   212          vm.expectRevert("OptimismPortal: withdrawal has already been finalized");
   213          optimismPortal2.finalizeWithdrawalTransaction(_defaultTx);
   214      }
   215  }
   216  
   217  contract OptimismPortal_CanAlwaysFinalizeAfterWindow is OptimismPortal2_Invariant_Harness {
   218      function setUp() public override {
   219          super.setUp();
   220  
   221          // Prove the withdrawal transaction
   222          optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof);
   223  
   224          // Warp past the proof maturity period.
   225          vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1);
   226  
   227          // Set the target contract to the portal proxy
   228          targetContract(address(optimismPortal2));
   229          // Exclude the proxy admin from the senders so that the proxy cannot be upgraded
   230          excludeSender(EIP1967Helper.getAdmin(address(optimismPortal2)));
   231      }
   232  
   233      /// @custom:invariant A withdrawal should **always** be able to be finalized `PROOF_MATURITY_DELAY_SECONDS` after
   234      ///                   it was successfully proven, if the game has resolved and passed the air-gap.
   235      ///
   236      ///                   This invariant asserts that there is no chain of calls that can be made that will prevent a
   237      ///                   withdrawal from being finalized exactly `PROOF_MATURITY_DELAY_SECONDS` after it was
   238      ///                   successfully proven and the game has resolved and passed the air-gap.
   239      function invariant_canAlwaysFinalize() external {
   240          uint256 bobBalanceBefore = address(bob).balance;
   241  
   242          optimismPortal2.finalizeWithdrawalTransaction(_defaultTx);
   243  
   244          assertEq(address(bob).balance, bobBalanceBefore + _defaultTx.value);
   245      }
   246  }