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