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 }