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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { StdUtils } from "forge-std/StdUtils.sol";
     5  import { Vm } from "forge-std/Vm.sol";
     6  import { OptimismPortal } from "src/L1/OptimismPortal.sol";
     7  import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
     8  import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
     9  import { Predeploys } from "src/libraries/Predeploys.sol";
    10  import { Constants } from "src/libraries/Constants.sol";
    11  import { Encoding } from "src/libraries/Encoding.sol";
    12  import { Hashing } from "src/libraries/Hashing.sol";
    13  import { Bridge_Initializer } from "test/setup/Bridge_Initializer.sol";
    14  
    15  contract RelayActor is StdUtils {
    16      // Storage slot of the l2Sender
    17      uint256 constant senderSlotIndex = 50;
    18  
    19      uint256 public numHashes;
    20      bytes32[] public hashes;
    21      bool public reverted = false;
    22  
    23      OptimismPortal op;
    24      L1CrossDomainMessenger xdm;
    25      Vm vm;
    26      bool doFail;
    27  
    28      constructor(OptimismPortal _op, L1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) {
    29          op = _op;
    30          xdm = _xdm;
    31          vm = _vm;
    32          doFail = _doFail;
    33      }
    34  
    35      /// @notice Relays a message to the `L1CrossDomainMessenger` with a random `version`,
    36      ///         and `_message`.
    37      function relay(uint8 _version, uint8 _value, bytes memory _message) external {
    38          address target = address(0x04); // ID precompile
    39          address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
    40  
    41          // Set the minimum gas limit to the cost of the identity precompile's execution for
    42          // the given message.
    43          // ID Precompile cost can be determined by calculating: 15 + 3 * data_word_length
    44          uint32 minGasLimit = uint32(15 + 3 * ((_message.length + 31) / 32));
    45  
    46          // set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
    47          vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
    48  
    49          // Restrict version to the range of [0, 1]
    50          _version = _version % 2;
    51  
    52          // Restrict the value to the range of [0, 1]
    53          // This is just so we get variance of calls with and without value. The ID precompile
    54          // will not reject value being sent to it.
    55          _value = _value % 2;
    56  
    57          // If the message should succeed, supply it `baseGas`. If not, supply it an amount of
    58          // gas that is too low to complete the call.
    59          uint256 gas = doFail ? bound(minGasLimit, 60_000, 80_000) : xdm.baseGas(_message, minGasLimit);
    60  
    61          // Compute the cross domain message hash and store it in `hashes`.
    62          // The `relayMessage` function will always encode the message as a version 1
    63          // message after checking that the V0 hash has not already been relayed.
    64          bytes32 _hash = Hashing.hashCrossDomainMessageV1(
    65              Encoding.encodeVersionedNonce(0, _version), sender, target, _value, minGasLimit, _message
    66          );
    67          hashes.push(_hash);
    68          numHashes += 1;
    69  
    70          // Make sure we've got a fresh message.
    71          vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false);
    72  
    73          // Act as the optimism portal and call `relayMessage` on the `L1CrossDomainMessenger` with
    74          // the outer min gas limit.
    75          vm.startPrank(address(op));
    76          if (!doFail) {
    77              vm.expectCallMinGas(address(0x04), _value, minGasLimit, _message);
    78          }
    79          try xdm.relayMessage{ gas: gas, value: _value }(
    80              Encoding.encodeVersionedNonce(0, _version), sender, target, _value, minGasLimit, _message
    81          ) { } catch {
    82              // If any of these calls revert, set `reverted` to true to fail the invariant test.
    83              // NOTE: This is to get around forge's invariant fuzzer ignoring reverted calls
    84              // to this function.
    85              reverted = true;
    86          }
    87          vm.stopPrank();
    88      }
    89  }
    90  
    91  contract XDM_MinGasLimits is Bridge_Initializer {
    92      RelayActor actor;
    93  
    94      function init(bool doFail) public virtual {
    95          // Set up the `L1CrossDomainMessenger` and `OptimismPortal` contracts.
    96          super.setUp();
    97  
    98          // Deploy a relay actor
    99          actor = new RelayActor(optimismPortal, l1CrossDomainMessenger, vm, doFail);
   100  
   101          // Give the portal some ether to send to `relayMessage`
   102          vm.deal(address(optimismPortal), type(uint128).max);
   103  
   104          // Target the `RelayActor` contract
   105          targetContract(address(actor));
   106  
   107          // Don't allow the estimation address to be the sender
   108          excludeSender(Constants.ESTIMATION_ADDRESS);
   109  
   110          // Don't allow the predeploys to be the senders
   111          uint160 prefix = uint160(0x420) << 148;
   112          for (uint256 i = 0; i < 2048; i++) {
   113              address addr = address(prefix | uint160(i));
   114              excludeContract(addr);
   115          }
   116  
   117          // Target the actor's `relay` function
   118          bytes4[] memory selectors = new bytes4[](1);
   119          selectors[0] = actor.relay.selector;
   120          targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors }));
   121      }
   122  }
   123  
   124  contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits {
   125      function setUp() public override {
   126          // Don't fail
   127          super.init(false);
   128      }
   129  
   130      /// @custom:invariant A call to `relayMessage` should succeed if at least the minimum gas limit
   131      ///                   can be supplied to the target context, there is enough gas to complete
   132      ///                   execution of `relayMessage` after the target context's execution is
   133      ///                   finished, and the target context did not revert.
   134      ///
   135      ///                   There are two minimum gas limits here:
   136      ///
   137      ///                   - The outer min gas limit is for the call from the `OptimismPortal` to the
   138      ///                     `L1CrossDomainMessenger`,  and it can be retrieved by calling the xdm's
   139      ///                     `baseGas` function with the `message` and inner limit.
   140      ///
   141      ///                   - The inner min gas limit is for the call from the
   142      ///                     `L1CrossDomainMessenger` to the target contract.
   143      function invariant_minGasLimits() external {
   144          uint256 length = actor.numHashes();
   145          for (uint256 i = 0; i < length; ++i) {
   146              bytes32 hash = actor.hashes(i);
   147              // The message hash is set in the successfulMessages mapping
   148              assertTrue(l1CrossDomainMessenger.successfulMessages(hash));
   149              // The message hash is not set in the failedMessages mapping
   150              assertFalse(l1CrossDomainMessenger.failedMessages(hash));
   151          }
   152          assertFalse(actor.reverted());
   153      }
   154  }
   155  
   156  contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits {
   157      function setUp() public override {
   158          // Do fail
   159          super.init(true);
   160      }
   161  
   162      /// @custom:invariant A call to `relayMessage` should assign the message hash to the
   163      ///                   `failedMessages` mapping if not enough gas is supplied to forward
   164      ///                   `minGasLimit` to the target context or if there is not enough gas to
   165      ///                   complete execution of `relayMessage` after the target context's execution
   166      ///                   is finished.
   167      ///
   168      ///                   There are two minimum gas limits here:
   169      ///
   170      ///                   - The outer min gas limit is for the call from the `OptimismPortal` to the
   171      ///                     `L1CrossDomainMessenger`,  and it can be retrieved by calling the xdm's
   172      ///                     `baseGas` function with the `message` and inner limit.
   173      ///
   174      ///                   - The inner min gas limit is for the call from the
   175      ///                     `L1CrossDomainMessenger` to the target contract.
   176      function invariant_minGasLimits() external {
   177          uint256 length = actor.numHashes();
   178          for (uint256 i = 0; i < length; ++i) {
   179              bytes32 hash = actor.hashes(i);
   180              // The message hash is not set in the successfulMessages mapping
   181              assertFalse(l1CrossDomainMessenger.successfulMessages(hash));
   182              // The message hash is set in the failedMessages mapping
   183              assertTrue(l1CrossDomainMessenger.failedMessages(hash));
   184          }
   185          assertFalse(actor.reverted());
   186      }
   187  }