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 }