// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import {DISPUTE_TIMEOUT_NOTARY} from "../../../contracts/libs/Constants.sol"; import { AlreadyExecuted, AlreadyFailed, DisputeTimeoutNotOver, GasLimitTooLow, GasSuppliedTooLow, IncorrectDestinationDomain, IncorrectOriginDomain, IncorrectMagicValue, IncorrectSnapshotRoot, MessageOptimisticPeriod, NotaryInDispute } from "../../../contracts/libs/Errors.sol"; import {AgentFlag} from "../../../contracts/libs/Structures.sol"; import {IExecutionHub} from "../../../contracts/interfaces/IExecutionHub.sol"; import {IStatementInbox} from "../../../contracts/interfaces/IStatementInbox.sol"; import {SNAPSHOT_MAX_STATES} from "../../../contracts/libs/memory/Snapshot.sol"; import {MessageStatus} from "../../../contracts/libs/Structures.sol"; import {ReentrantApp} from "../../harnesses/client/ReentrantApp.t.sol"; import {RevertingApp} from "../../harnesses/client/RevertingApp.t.sol"; import {MessageRecipientMock} from "../../mocks/client/MessageRecipientMock.t.sol"; import {fakeSnapshot} from "../../utils/libs/FakeIt.t.sol"; import {Random} from "../../utils/libs/Random.t.sol"; import { ReceiptLib, MessageFlag, RawAttestation, RawBaseMessage, RawCallData, RawExecReceipt, RawHeader, RawMessage, RawSnapshot, RawState, RawStateIndex, RawTips } from "../../utils/libs/SynapseStructs.t.sol"; import {AgentSecuredTest} from "../base/AgentSecured.t.sol"; // solhint-disable func-name-mixedcase // solhint-disable no-empty-blocks // solhint-disable ordering abstract contract ExecutionHubTest is AgentSecuredTest { address internal recipient; address internal executor; address internal executorNew; uint8 internal cachedStateIndex; bytes internal msgPayload; bytes32 internal msgLeaf; function setUp() public virtual override { super.setUp(); recipient = address(new MessageRecipientMock()); executor = makeAddr("Executor"); executorNew = makeAddr("Executor New"); } /// @notice Prevents this contract from being included in the coverage report function testExecutionHub() external {} // ═══════════════════════════════════════ TESTS: EXECUTE BASE MESSAGES ════════════════════════════════════════════ function test_execute_base( RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm, uint32 timePassed, uint64 gasLimit ) public { // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); gasLimit = uint64(bound(gasLimit, rbm.request.gasLimit, 2_000_000)); // receiveBaseMessage(origin, nonce, sender, proofMaturity, version, message) bytes memory expectedCall = abi.encodeWithSelector( MessageRecipientMock.receiveBaseMessage.selector, rh.origin, rh.nonce, rbm.sender, timePassed, rbm.request.version, rbm.content ); // expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) vm.expectCall(recipient, 0, gasLimit, expectedCall); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, true); vm.prank(executor); msgLeaf = rm.castToMessage().leaf(); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, gasLimit); bytes memory rcptPayload = verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Success, executor, executor); verify_receipt_valid(rcptPayload); } function test_execute_base_recipientEOA(Random memory random) public { recipient = random.nextAddress(); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, false); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); bytes memory rcptPayloadFirst = verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Failed, executor, address(0)); verify_receipt_valid(rcptPayloadFirst); } function test_execute_base_recipientReverted_thenSuccess(Random memory random) public { recipient = address(new RevertingApp()); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, false); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); bytes memory rcptPayloadFirst = verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Failed, executor, address(0)); verify_receipt_valid(rcptPayloadFirst); // Retry the same failed message RevertingApp(payable(recipient)).toggleRevert(false); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, true); vm.prank(executorNew); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); bytes memory rcptPayloadSecond = verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Success, executor, executorNew); // Both receipts (historical and current) should be valid verify_receipt_valid(rcptPayloadFirst); verify_receipt_valid(rcptPayloadSecond); cachedStateIndex = uint8(sm.rsi.stateIndex); } function test_execute_base_recipientReverted_twice(Random memory random) public { recipient = address(new RevertingApp()); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, false); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); bytes memory rcptPayload = verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Failed, executor, address(0)); verify_receipt_valid(rcptPayload); // Retry the same failed message vm.expectRevert(AlreadyFailed.selector); vm.prank(executorNew); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); } function test_execute_base_revert_alreadyExecuted(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); vm.expectRevert(AlreadyExecuted.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); } function test_execute_base_revert_notaryInDispute(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // initiate dispute openDispute({guard: domains[0].agent, notary: domains[DOMAIN_LOCAL].agent}); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectRevert(NotaryInDispute.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_notaryWonDisputeTimeout() public { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("Random Salt 1"); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // initiate dispute openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); // Make sure that optimistic period is over skip(rh.optimisticPeriod); resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: agentIndex[notary]}); skip(DISPUTE_TIMEOUT_NOTARY - 1); vm.expectRevert(DisputeTimeoutNotOver.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); } function test_execute_base_afterNotaryDisputeTimeout() public { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("Random Salt 2"); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // initiate dispute openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); // Make sure that optimistic period is over skip(rh.optimisticPeriod); resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: agentIndex[notary]}); skip(DISPUTE_TIMEOUT_NOTARY); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Success, executor, executor); } function test_execute_base_revert_snapRootUnknown(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = createSnapshotProof(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectRevert(IncorrectSnapshotRoot.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_optimisticPeriodNotOver(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); vm.assume(rh.optimisticPeriod != 0); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is NOT over uint32 timePassed = random.nextUint32() % rh.optimisticPeriod; skip(timePassed); vm.expectRevert(MessageOptimisticPeriod.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_optimisticPeriodMinus1Second() public { Random memory random = Random("random salt"); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); vm.assume(rh.optimisticPeriod != 0); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is NOT over uint32 timePassed = rh.optimisticPeriod - 1; skip(timePassed); vm.expectRevert(MessageOptimisticPeriod.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); } function test_execute_base_revert_gasLimitTooLow(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); // Make sure gas limit is lower than requested uint64 gasLimit = random.nextUint64() % rbm.request.gasLimit; vm.expectRevert(GasLimitTooLow.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, gasLimit); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_gasSuppliedTooLow(Random memory random) public { // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectRevert(GasSuppliedTooLow.selector); vm.prank(executor); // Limit amount of gas for the whole call testedEH().execute{gas: rbm.request.gasLimit + 20_000}( msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit ); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_wrongDestination(Random memory random, uint32 destination_) public { vm.assume(destination_ != localDomain()); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, destination_); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); vm.expectRevert(IncorrectDestinationDomain.selector); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); verify_messageStatusNone(msgLeaf); } function test_execute_base_revert_originSameDomain() public { RawBaseMessage memory rbm; rbm.sender = rbm.recipient = addressToBytes32(recipient); RawMessage memory rm; rm.header.flag = uint8(MessageFlag.Base); rm.header.origin = localDomain(); rm.header.destination = localDomain(); rm.header.nonce = 1; rm.header.optimisticPeriod = 1 seconds; rm.body = rbm.formatBaseMessage(); msgPayload = rm.formatMessage(); vm.expectRevert(IncorrectOriginDomain.selector); testedEH().execute(msgPayload, new bytes32[](0), new bytes32[](0), 0, 0); } function test_execute_base_revert_reentrancy(Random memory random) public { recipient = address(new ReentrantApp()); // Create some simple data (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) = createDataRevertTest(random); // Create messages and get origin proof RawMessage memory rm = createBaseMessages(rbm, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over uint32 timePassed = random.nextUint32(); timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); ReentrantApp(recipient).prepare(msgPayload, originProof, snapProof, sm.rsi.stateIndex); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, rbm.request.gasLimit); // We allow message recipient to revert, which is what supposed to happen if they try to reenter verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Failed, executor, address(0)); } // ══════════════════════════════════════ TESTS: EXECUTE MANAGER MESSAGES ══════════════════════════════════════════ function test_execute_manager(RawHeader memory rh, SnapshotMock memory sm, uint32 timePassed, uint64 gasLimit) public { // Create messages and get origin proof RawMessage memory rm = createManagerMessages(lightManager.remoteMockFunc.selector, rh, localDomain()); msgPayload = rm.formatMessage(); msgLeaf = rm.castToMessage().leaf(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (bytes32 snapRoot, bytes32[] memory snapProof) = prepareExecution(sm); // Make sure that optimistic period is over timePassed = uint32(bound(timePassed, rh.optimisticPeriod, rh.optimisticPeriod + 1 days)); skip(timePassed); // expectCall(address callee, bytes calldata data) // remoteMockFunc(msgOrigin, proofMaturity, data); data = rh.nonce vm.expectCall( localAgentManager(), abi.encodeWithSelector(bondingManager.remoteMockFunc.selector, rh.origin, timePassed, rh.nonce) ); vm.expectEmit(); emit Executed(rh.origin, msgLeaf, true); vm.prank(executor); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, gasLimit); verify_messageStatus(msgLeaf, snapRoot, sm.rsi.stateIndex, MessageStatus.Success, executor, executor); } function test_execute_manager_revert_incorrectMagicValue() public { // Use empty values - these would be filled later in test preparation RawHeader memory rh; SnapshotMock memory sm; // Create messages and get origin proof RawMessage memory rm = createManagerMessages(lightManager.sensitiveMockFunc.selector, rh, localDomain()); msgPayload = rm.formatMessage(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); vm.expectRevert(IncorrectMagicValue.selector); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, 0); } function test_execute_manager_revert_noMagicValue() public { // Use empty values - these would be filled later in test preparation RawHeader memory rh; SnapshotMock memory sm; // Create messages and get origin proof RawMessage memory rm = createManagerMessages(lightManager.sensitiveMockFuncVoid.selector, rh, localDomain()); msgPayload = rm.formatMessage(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); vm.expectRevert(IncorrectMagicValue.selector); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, 0); } function test_execute_manager_revert_magicMoreThan32Bytes() public { // Use empty values - these would be filled later in test preparation RawHeader memory rh; SnapshotMock memory sm; // Create messages and get origin proof RawMessage memory rm = createManagerMessages(lightManager.sensitiveMockFuncOver32Bytes.selector, rh, localDomain()); msgPayload = rm.formatMessage(); bytes32[] memory originProof = getLatestProof(rh.nonce - 1); // Create snapshot proof adjustSnapshot(sm); (, bytes32[] memory snapProof) = prepareExecution(sm); vm.expectRevert(IncorrectMagicValue.selector); testedEH().execute(msgPayload, originProof, snapProof, sm.rsi.stateIndex, 0); } // ══════════════════════════════════════════ TESTS: INVALID RECEIPTS ══════════════════════════════════════════════ function test_verifyReceipt_invalid_msgStatusNone(RawExecReceipt memory re) public { vm.assume(testedEH().messageStatus(re.messageHash) == MessageStatus.None); vm.assume(re.origin != localDomain()); re.destination = localDomain(); verify_receipt_invalid(re); } function test_verifyReceipt_invalid_msgStatusSuccess(uint256 mask) public { test_execute_base_recipientReverted_thenSuccess(Random(bytes32(mask))); RawExecReceipt memory re = RawExecReceipt({ origin: DOMAIN_REMOTE, destination: localDomain(), messageHash: getLeaf(0), snapshotRoot: getSnapshotRoot(), stateIndex: cachedStateIndex, attNotary: domains[DOMAIN_LOCAL].agent, firstExecutor: executor, finalExecutor: executorNew }); // Check that data we start with is valid. Use require() to break the test execution early. require(testedEH().isValidReceipt(re.formatReceipt()), "Incorrect initial receipt data"); RawExecReceipt memory mre = re.modifyReceipt(mask); verify_receipt_invalid(mre); } // ═════════════════════════════════════════════════ VERIFIERS ═════════════════════════════════════════════════════ function verify_messageStatusNone(bytes32 messageHash) public { verify_messageStatus(messageHash, bytes32(0), 0, MessageStatus.None, address(0), address(0)); } function verify_messageStatus( bytes32 messageHash, bytes32 snapRoot, uint8 stateIndex, MessageStatus flag, address firstExecutor, address finalExecutor ) public returns (bytes memory rcptPayload) { MessageStatus flag_ = testedEH().messageStatus(messageHash); assertEq(uint8(flag_), uint8(flag), "!flag"); rcptPayload = testedEH().messageReceipt(messageHash); if (flag == MessageStatus.None) { assertEq(rcptPayload.length, 0, "!receipt: empty"); } else { address attNotary = domains[DOMAIN_LOCAL].agent; assertEq( rcptPayload, ReceiptLib.formatReceipt( DOMAIN_REMOTE, localDomain(), messageHash, snapRoot, uint8(stateIndex), attNotary, firstExecutor, finalExecutor ) ); } } function verify_receipt_valid(bytes memory rcptPayload) public { assertTrue(testedEH().isValidReceipt(rcptPayload)); address notary = domains[DOMAIN_LOCAL].agent; bytes memory rcptSignature = signReceipt(notary, rcptPayload); vm.recordLogs(); assertTrue(IStatementInbox(localInbox()).verifyReceipt(rcptPayload, rcptSignature)); assertEq(vm.getRecordedLogs().length, 0); } function verify_receipt_invalid(RawExecReceipt memory re) public { bytes memory rcptPayload = re.formatReceipt(); assertFalse(testedEH().isValidReceipt(rcptPayload)); address notary = domains[DOMAIN_LOCAL].agent; bytes memory rcptSignature = signReceipt(notary, rcptPayload); // TODO: check that anyone could make the call expectStatusUpdated(AgentFlag.Fraudulent, DOMAIN_LOCAL, notary); expectDisputeResolved(0, notary, address(0), address(this)); // expectAgentSlashed(localDomain(), notary, address(this)); assertFalse(IStatementInbox(localInbox()).verifyReceipt(rcptPayload, rcptSignature)); } // ══════════════════════════════════════════════════ HELPERS ══════════════════════════════════════════════════════ /// @notice Prepares execution of the created messages function prepareExecution(SnapshotMock memory sm) public virtual returns (bytes32 snapRoot, bytes32[] memory snapProof); function createBaseMessages(RawBaseMessage memory rbm, RawHeader memory rh, uint32 destination_) public returns (RawMessage memory rm) { adjustHeader(rh, destination_); rbm.recipient = addressToBytes32(recipient); // Set sensible limitations for tips/request rbm.tips.boundTips(2 ** 32); rbm.request.gasLimit = uint64(bound(rbm.request.gasLimit, 50_000, 200_000)); rh.flag = uint8(MessageFlag.Base); rm = RawMessage(rh, rbm.formatBaseMessage()); createMessages(rh.nonce, rm); } function createManagerMessages(bytes4 selector, RawHeader memory rh, uint32 destination_) public returns (RawMessage memory rm) { adjustHeader(rh, destination_); RawCallData memory rcd = RawCallData({selector: selector, args: abi.encode(rh.nonce)}); rh.flag = uint8(MessageFlag.Manager); rm = RawMessage(rh, rcd.formatCallData()); createMessages(rh.nonce, rm); } function createMessages(uint32 msgNonce, RawMessage memory rm) public { for (uint32 nonce = 1; nonce <= MESSAGES; ++nonce) { if (nonce == msgNonce) { insertMessage(rm.castToMessage().leaf()); } else { insertMessage(keccak256(abi.encode("Mocked payload", nonce))); } } } /// @notice Creates some simple data for the revert test function createDataRevertTest(Random memory random) public pure returns (RawBaseMessage memory rbm, RawHeader memory rh, SnapshotMock memory sm) { rbm.sender = random.next(); rbm.content = "Test content"; rbm.tips = RawTips(1, 1, 1, 1); rh.nonce = 1; rh.optimisticPeriod = random.nextUint32(); sm = SnapshotMock(random.nextState(), RawStateIndex(random.nextUint8(), random.nextUint256())); sm.rsi.boundStateIndex(); } /// @notice Sets realistic values for the message header function adjustHeader(RawHeader memory rh, uint32 destination_) public view { rh.origin = DOMAIN_REMOTE; rh.nonce = uint32(bound(rh.nonce, 1, MESSAGES)); rh.destination = destination_; rh.optimisticPeriod = rh.optimisticPeriod % 1 days; } function adjustState(RawState memory rs) public view { rs.origin = DOMAIN_REMOTE; rs.nonce = MESSAGES; rs.root = getRoot(rs.nonce); } function adjustSnapshot(SnapshotMock memory sm) public view { adjustState(sm.rs); sm.rsi.boundStateIndex(); } /// @notice Returns address of the tested contract function localContract() public view override returns (address) { return localDestination(); } /// @notice Returns tested contract as IExecutionHub function testedEH() public view returns (IExecutionHub) { return IExecutionHub(localContract()); } }