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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test } from "forge-std/Test.sol";
     5  import { StdUtils } from "forge-std/StdUtils.sol";
     6  import { Vm } from "forge-std/Vm.sol";
     7  import { SafeCall } from "src/libraries/SafeCall.sol";
     8  import { InvariantTest } from "test/invariants/InvariantTest.sol";
     9  
    10  contract SafeCall_Succeeds_Invariants is InvariantTest {
    11      SafeCaller_Actor actor;
    12  
    13      function setUp() public override {
    14          super.setUp();
    15          // Create a new safe caller actor.
    16          actor = new SafeCaller_Actor(vm, false);
    17  
    18          // Set the caller to this contract
    19          targetSender(address(this));
    20  
    21          // Target the safe caller actor.
    22          targetContract(address(actor));
    23  
    24          // Give the actor some ETH to work with
    25          vm.deal(address(actor), type(uint128).max);
    26      }
    27  
    28      /// @custom:invariant If `callWithMinGas` performs a call, then it must always
    29      ///                   provide at least the specified minimum gas limit to the subcontext.
    30      ///
    31      ///                   If the check for remaining gas in `SafeCall.callWithMinGas` passes, the
    32      ///                   subcontext of the call below it must be provided at least `minGas` gas.
    33      function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public {
    34          assertEq(actor.numCalls(), 0, "no failed calls allowed");
    35      }
    36  
    37      function performSafeCallMinGas(address to, uint64 minGas) external payable {
    38          SafeCall.callWithMinGas(to, minGas, msg.value, hex"");
    39      }
    40  }
    41  
    42  contract SafeCall_Fails_Invariants is InvariantTest {
    43      SafeCaller_Actor actor;
    44  
    45      function setUp() public override {
    46          super.setUp();
    47          // Create a new safe caller actor.
    48          actor = new SafeCaller_Actor(vm, true);
    49  
    50          // Set the caller to this contract
    51          targetSender(address(this));
    52  
    53          // Target the safe caller actor.
    54          targetContract(address(actor));
    55  
    56          // Give the actor some ETH to work with
    57          vm.deal(address(actor), type(uint128).max);
    58      }
    59  
    60      /// @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass
    61      ///                   to the subcontext.
    62      ///
    63      ///                   If there is not enough gas in the callframe to ensure that
    64      ///                   `callWithMinGas` can provide the specified minimum gas limit
    65      ///                   to the subcontext of the call, then `callWithMinGas` must revert.
    66      function invariant_callWithMinGas_neverForwardsMinGas_reverts() public {
    67          assertEq(actor.numCalls(), 0, "no successful calls allowed");
    68      }
    69  
    70      function performSafeCallMinGas(address to, uint64 minGas) external payable {
    71          SafeCall.callWithMinGas(to, minGas, msg.value, hex"");
    72      }
    73  }
    74  
    75  contract SafeCaller_Actor is StdUtils {
    76      bool internal immutable FAILS;
    77  
    78      Vm internal vm;
    79      uint256 public numCalls;
    80  
    81      constructor(Vm _vm, bool _fails) {
    82          vm = _vm;
    83          FAILS = _fails;
    84      }
    85  
    86      function performSafeCallMinGas(uint64 gas, uint64 minGas, address to, uint8 value) external {
    87          // Only send to EOAs - we exclude the console as it has no code but reverts when called
    88          // with a selector that doesn't exist due to the foundry hook.
    89          vm.assume(to.code.length == 0 && to != 0x000000000000000000636F6e736F6c652e6c6f67);
    90  
    91          // Bound the minimum gas amount to [2500, type(uint48).max]
    92          minGas = uint64(bound(minGas, 2500, type(uint48).max));
    93          if (FAILS) {
    94              // Bound the gas passed to [minGas, ((minGas * 64) / 63)]
    95              gas = uint64(bound(gas, minGas, (minGas * 64) / 63));
    96          } else {
    97              // Bound the gas passed to
    98              // [((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max]
    99              // The extra 1000 gas is to account for the gas used by the `SafeCall.call` call
   100              // itself.
   101              gas = uint64(bound(gas, ((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max));
   102          }
   103  
   104          vm.expectCallMinGas(to, value, minGas, hex"");
   105          bool success = SafeCall.call(
   106              msg.sender,
   107              gas,
   108              value,
   109              abi.encodeWithSelector(SafeCall_Succeeds_Invariants.performSafeCallMinGas.selector, to, minGas)
   110          );
   111  
   112          if (success && FAILS) numCalls++;
   113          if (!FAILS && !success) numCalls++;
   114      }
   115  }