// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import {DISPUTE_TIMEOUT_NOTARY} from "../../contracts/libs/Constants.sol"; import {CallerNotInbox, DisputeTimeoutNotOver, NotaryInDispute, OutdatedNonce} from "../../contracts/libs/Errors.sol"; import {SNAPSHOT_MAX_STATES} from "../../contracts/libs/memory/Snapshot.sol"; import {DisputeFlag} from "../../contracts/libs/Structures.sol"; import {IAgentSecured} from "../../contracts/interfaces/IAgentSecured.sol"; import {ChainGas, GasData, InterfaceDestination} from "../../contracts/Destination.sol"; import {Versioned} from "../../contracts/base/Version.sol"; import {fakeSnapshot} from "../utils/libs/FakeIt.t.sol"; import {Random} from "../utils/libs/Random.t.sol"; import {RawAttestation, RawSnapshot, RawState, RawStateIndex} from "../utils/libs/SynapseStructs.t.sol"; import {AgentFlag, AgentStatus, Destination, LightManager, SynapseTest} from "../utils/SynapseTest.t.sol"; import {ExecutionHubTest} from "./hubs/ExecutionHub.t.sol"; // solhint-disable func-name-mixedcase // solhint-disable no-empty-blocks // solhint-disable ordering contract DestinationTest is ExecutionHubTest { // Deploy Production version of Destination and mocks for everything else constructor() SynapseTest(DEPLOY_PROD_DESTINATION) {} // ═══════════════════════════════════════════════ TESTS: SETUP ════════════════════════════════════════════════════ function test_setupCorrectly() public { Destination dst = Destination(localDestination()); // Check Agents: all Agents are known in LightManager post-setup for (uint256 d = 0; d < allDomains.length; ++d) { uint32 domain = allDomains[d]; for (uint256 i = 0; i < domains[domain].agents.length; ++i) { address agent = domains[domain].agents[i]; checkAgentStatus(agent, dst.agentStatus(agent), AgentFlag.Active); } } // Check AgentManager and Inbox assertEq(dst.agentManager(), localAgentManager(), "!agentManager"); assertEq(dst.inbox(), localInbox(), "!inbox"); // Check version assertEq(dst.version(), LATEST_VERSION, "!version"); // Check pending Agent Merkle Root (bool rootPending) = dst.passAgentRoot(); assertFalse(rootPending); } function test_constructor_revert_chainIdOverflow() public { vm.chainId(2 ** 32); vm.expectRevert("SafeCast: value doesn't fit in 32 bits"); new Destination({synapseDomain_: 1, agentManager_: address(2), inbox_: address(3)}); } function test_cleanSetup(Random memory random) public override { uint32 domain = random.nextUint32(); vm.assume(domain != DOMAIN_SYNAPSE); address caller = random.nextAddress(); vm.chainId(uint256(domain)); LightManager agentManager = new LightManager(DOMAIN_SYNAPSE); address inbox_ = random.nextAddress(); bytes32 agentRoot = random.next(); Destination cleanContract = new Destination(DOMAIN_SYNAPSE, address(agentManager), inbox_); agentManager.initialize(address(0), address(cleanContract), inbox_); vm.prank(caller); cleanContract.initialize(agentRoot); assertEq(cleanContract.owner(), caller, "!owner"); assertEq(cleanContract.localDomain(), domain, "!localDomain"); assertEq(cleanContract.agentManager(), address(agentManager), "!agentManager"); assertEq(cleanContract.inbox(), inbox_, "!inbox"); assertEq(cleanContract.nextAgentRoot(), agentRoot, "!nextAgentRoot"); } function initializeLocalContract() public override { Destination(localContract()).initialize(0); } // ════════════════════════════════════════════════ OTHER TESTS ════════════════════════════════════════════════════ function test_getAttestation(Random memory random) public { uint256 amount = 10; bytes[] memory attPayloads = new bytes[](amount); bytes[] memory attSignatures = new bytes[](amount); for (uint32 index = 0; index < amount; ++index) { address notary = domains[localDomain()].agents[random.nextUint256() % DOMAIN_AGENTS]; RawSnapshot memory rs = random.nextSnapshot(); RawAttestation memory ra = random.nextAttestation(rs, index + 1); uint256[] memory snapGas = rs.snapGas(); (attPayloads[index], attSignatures[index]) = signAttestation(notary, ra); lightInbox.submitAttestation(attPayloads[index], attSignatures[index], ra._agentRoot, snapGas); } for (uint32 index = 0; index < amount; ++index) { (bytes memory attPayload, bytes memory attSignature) = InterfaceDestination(localDestination()).getAttestation(index); assertEq(attPayload, attPayloads[index], "!payload"); assertEq(attSignature, attSignatures[index], "!signature"); } } function test_submitAttestation(RawAttestation memory ra, uint32 rootSubmittedAt) public { vm.assume(ra.nonce != 0); RawSnapshot memory rs = Random(ra.snapRoot).nextSnapshot(); ra._snapGasHash = rs.snapGasHash(); ra.setDataHash(); uint256[] memory snapGas = rs.snapGas(); address notary = domains[DOMAIN_LOCAL].agent; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); vm.warp(rootSubmittedAt); vm.expectEmit(); emit AttestationAccepted(DOMAIN_LOCAL, notary, attPayload, attSig); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); (uint48 snapRootTime,,) = InterfaceDestination(localDestination()).destStatus(); assertEq(snapRootTime, rootSubmittedAt); assertEq(InterfaceDestination(localDestination()).lastAttestationNonce(agentIndex[notary]), ra.nonce); } function test_submitAttestation_updatesAgentRoot(RawAttestation memory ra, uint32 rootSubmittedAt) public { vm.assume(ra.nonce != 0); RawSnapshot memory rs = Random(ra.snapRoot).nextSnapshot(); ra._snapGasHash = rs.snapGasHash(); ra.setDataHash(); uint256[] memory snapGas = rs.snapGas(); bytes32 agentRootLM = lightManager.agentRoot(); vm.assume(ra._agentRoot != agentRootLM); address notary = domains[DOMAIN_LOCAL].agent; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); vm.warp(rootSubmittedAt); vm.expectEmit(); emit AttestationAccepted(DOMAIN_LOCAL, notary, attPayload, attSig); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); (, uint40 agentRootTime, uint32 index) = InterfaceDestination(localDestination()).destStatus(); // Check that values were assigned assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), ra._agentRoot); assertEq(agentRootTime, rootSubmittedAt); assertEq(index, agentIndex[notary]); } function test_submitAttestation_doesNotOverwritePending( RawAttestation memory firstRA, RawAttestation memory secondRA, uint32 firstRootSubmittedAt, uint32 timePassed ) public { vm.assume(firstRA.nonce != 0 && secondRA.nonce != 0); bytes32 agentRootLM = lightManager.agentRoot(); vm.assume(firstRA._agentRoot != agentRootLM); vm.assume(firstRA.snapRoot != secondRA.snapRoot); test_submitAttestation(firstRA, firstRootSubmittedAt); timePassed = timePassed % AGENT_ROOT_OPTIMISTIC_PERIOD; skip(timePassed); // Form a second attestation: Notary 1 RawSnapshot memory rs = Random(secondRA.snapRoot).nextSnapshot(); secondRA._snapGasHash = rs.snapGasHash(); secondRA.setDataHash(); uint256[] memory snapGas = rs.snapGas(); address notaryF = domains[DOMAIN_LOCAL].agents[0]; address notaryS = domains[DOMAIN_LOCAL].agents[1]; (bytes memory attPayload, bytes memory attSig) = signAttestation(notaryS, secondRA); vm.expectEmit(); emit AttestationAccepted(DOMAIN_LOCAL, notaryS, attPayload, attSig); assertTrue(lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, snapGas)); (uint40 snapRootTime, uint40 agentRootTime, uint32 index) = InterfaceDestination(localDestination()).destStatus(); assertEq(snapRootTime, block.timestamp); assertEq(agentRootTime, firstRootSubmittedAt); assertEq(index, agentIndex[notaryF]); } function test_acceptAttestation_revert_notInbox(address caller) public { vm.assume(caller != localInbox()); vm.expectRevert(CallerNotInbox.selector); vm.prank(caller); InterfaceDestination(localDestination()).acceptAttestation(0, 0, "", 0, new ChainGas[](0)); } function test_acceptAttestation_revert_blockTimestampOverflow() public { address notary = domains[DOMAIN_LOCAL].agent; Random memory random = Random("salt"); RawSnapshot memory rawSnap = random.nextSnapshot(); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); uint256[] memory snapGas = rawSnap.snapGas(); (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); vm.warp(2 ** 40); vm.expectRevert("SafeCast: value doesn't fit in 40 bits"); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); } function test_acceptAttestation_revert_notaryInDispute(uint256 notaryId) public { address notary = domains[DOMAIN_LOCAL].agents[notaryId % DOMAIN_AGENTS]; openDispute({guard: domains[0].agent, notary: notary}); vm.prank(address(lightInbox)); vm.expectRevert(NotaryInDispute.selector); InterfaceDestination(localDestination()).acceptAttestation(agentIndex[notary], 0, "", 0, new ChainGas[](0)); } function test_acceptAttestation_revert_notaryWonDisputeTimeout() public { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("salt"); RawSnapshot memory rawSnap = random.nextSnapshot(); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); uint256[] memory snapGas = rawSnap.snapGas(); (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); skip(7 days); resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: agentIndex[notary]}); skip(DISPUTE_TIMEOUT_NOTARY - 1); vm.expectRevert(DisputeTimeoutNotOver.selector); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); } function test_acceptAttestation_afterNotaryDisputeTimeout() public { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("salt"); RawSnapshot memory rawSnap = random.nextSnapshot(); bytes32 snapRoot = rawSnap.castToSnapshot().calculateRoot(); // Sanity check assert(testedEH().getAttestationNonce(snapRoot) == 0); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); uint256[] memory snapGas = rawSnap.snapGas(); (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); skip(7 days); resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: agentIndex[notary]}); skip(DISPUTE_TIMEOUT_NOTARY); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); // Snapshot root should be known to ExecutionHub (Destination) assertGt(testedEH().getAttestationNonce(snapRoot), 0); } function test_acceptAttestation_revert_lowerNonce() public { Random memory random = Random("salt"); RawAttestation memory firstRA = random.nextAttestation({nonce: 2}); test_submitAttestation({ra: firstRA, rootSubmittedAt: 1000}); skip(100); RawSnapshot memory rawSnap = random.nextSnapshot(); uint256[] memory snapGas = rawSnap.snapGas(); RawAttestation memory secondRA = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); address notary = domains[DOMAIN_LOCAL].agent; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, secondRA); vm.expectRevert(OutdatedNonce.selector); lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, snapGas); } function test_acceptAttestation_revert_sameNonce() public { Random memory random = Random("salt"); RawSnapshot memory rawSnap = random.nextSnapshot(); uint256[] memory snapGas = rawSnap.snapGas(); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); address notary = domains[DOMAIN_LOCAL].agent; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); skip(100); vm.expectRevert(OutdatedNonce.selector); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); } function test_acceptAttestation_passesAgentRoot( RawAttestation memory firstRA, RawAttestation memory secondRA, uint32 firstRootSubmittedAt ) public { bytes32 agentRootLM = lightManager.agentRoot(); vm.assume(firstRA._agentRoot != agentRootLM); vm.assume(firstRA.snapRoot != secondRA.snapRoot); vm.assume(secondRA.nonce > firstRA.nonce); test_submitAttestation(firstRA, firstRootSubmittedAt); skip(AGENT_ROOT_OPTIMISTIC_PERIOD); // Form a second attestation: Notary 1 RawSnapshot memory rs = Random(secondRA.snapRoot).nextSnapshot(); secondRA._snapGasHash = rs.snapGasHash(); secondRA.setDataHash(); uint256[] memory snapGas = rs.snapGas(); address notaryS = domains[DOMAIN_LOCAL].agents[1]; (bytes memory attPayload, bytes memory attSig) = signAttestation(notaryS, secondRA); uint256 newRootTimestamp = block.timestamp; assertTrue(lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, snapGas)); (uint40 snapRootTime, uint40 agentRootTime, uint32 index) = InterfaceDestination(localDestination()).destStatus(); // Dest status should point to the new root assertEq(snapRootTime, newRootTimestamp); assertEq(agentRootTime, newRootTimestamp); assertEq(index, agentIndex[notaryS]); // New agent root should be pending in Destination assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), secondRA._agentRoot); // Should update the Agent Merkle Root assertEq(lightManager.agentRoot(), firstRA._agentRoot); } function test_acceptAttestation_success_diffNotary_lowerNonce() public { Random memory random = Random("salt"); RawAttestation memory firstRA = random.nextAttestation({nonce: 2}); test_submitAttestation({ra: firstRA, rootSubmittedAt: 1000}); skip(100); RawSnapshot memory rawSnap = random.nextSnapshot(); uint256[] memory snapGas = rawSnap.snapGas(); RawAttestation memory secondRA = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); address notary = domains[DOMAIN_LOCAL].agents[1]; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, secondRA); bool success = lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, snapGas); assertTrue(success); } function test_acceptAttestation_success_diffNotary_diffAtt_sameNonce() public { Random memory random = Random("salt"); RawAttestation memory firstRA = random.nextAttestation({nonce: 2}); test_submitAttestation({ra: firstRA, rootSubmittedAt: 1000}); skip(100); RawSnapshot memory rawSnap = random.nextSnapshot(); uint256[] memory snapGas = rawSnap.snapGas(); RawAttestation memory secondRA = random.nextAttestation({rawSnap: rawSnap, nonce: 2}); address notary = domains[DOMAIN_LOCAL].agents[1]; (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, secondRA); bool success = lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, snapGas); assertTrue(success); } function test_passAgentRoot_optimisticPeriodNotOver( RawAttestation memory ra, uint32 rootSubmittedAt, uint32 timePassed ) public { bytes32 agentRootLM = lightManager.agentRoot(); vm.assume(ra._agentRoot != agentRootLM); // Submit attestation that updates `nextAgentRoot` test_submitAttestation_updatesAgentRoot(ra, rootSubmittedAt); timePassed = timePassed % AGENT_ROOT_OPTIMISTIC_PERIOD; skip(timePassed); bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); assertTrue(rootPending); assertEq(lightManager.agentRoot(), agentRootLM); } function test_passAgentRoot_optimisticPeriodOver(RawAttestation memory ra, uint32 rootSubmittedAt) public { // Submit attestation that updates `nextAgentRoot` test_submitAttestation_updatesAgentRoot(ra, rootSubmittedAt); skip(AGENT_ROOT_OPTIMISTIC_PERIOD); bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); assertFalse(rootPending); assertEq(lightManager.agentRoot(), ra._agentRoot); } /// @dev Submits a mock attestation and opens a dispute for the Notary that signed it. function prepareAgentRootDisputeTest() internal returns (bytes32 newAgentRoot) { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("salt"); RawSnapshot memory rawSnap = random.nextSnapshot(); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); newAgentRoot = ra._agentRoot; uint256[] memory snapGas = rawSnap.snapGas(); (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); // Sanity check assert(InterfaceDestination(localDestination()).nextAgentRoot() == newAgentRoot); openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); } /// @dev Resolves test dispute above in favor of the Notary. function prepareNotaryWonDisputeTest() internal { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: agentIndex[notary]}); } function test_passAgentRoot_notaryInDispute_optimisticPeriodNotOver() public { bytes32 oldRoot = lightManager.agentRoot(); prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD - 1); bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should not pass the root assertEq(lightManager.agentRoot(), oldRoot); // Should clear pending assertFalse(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), oldRoot); } function test_passAgentRoot_notaryInDispute_optimisticPeriodOver() public { bytes32 oldRoot = lightManager.agentRoot(); prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD); bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should not pass the root assertEq(lightManager.agentRoot(), oldRoot); // Should clear pending assertFalse(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), oldRoot); } function test_passAgentRoot_notaryWonDisputeTimeout_optimisticPeriodNotOver() public { bytes32 oldRoot = lightManager.agentRoot(); bytes32 newRoot = prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD - DISPUTE_TIMEOUT_NOTARY); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY - 1); // Time since attestation was submitted: AGENT_ROOT_OPTIMISTIC_PERIOD - 1 // Time sinceNotary won the dispute: DISPUTE_TIMEOUT_NOTARY - 1 bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should not pass the root assertEq(lightManager.agentRoot(), oldRoot); // Should not clear pending assertTrue(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), newRoot); } function test_passAgentRoot_notaryWonDisputeTimeout_optimisticPeriodOver() public { bytes32 oldRoot = lightManager.agentRoot(); bytes32 newRoot = prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD - (DISPUTE_TIMEOUT_NOTARY - 1)); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY - 1); // Time since attestation was submitted: AGENT_ROOT_OPTIMISTIC_PERIOD // Time sinceNotary won the dispute: DISPUTE_TIMEOUT_NOTARY - 1 bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should not pass the root assertEq(lightManager.agentRoot(), oldRoot); // Should not clear pending assertTrue(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), newRoot); } function test_passAgentRoot_afterNotaryDisputeTimeout_optimisticPeriodNotOver() public { bytes32 oldRoot = lightManager.agentRoot(); bytes32 newRoot = prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD - 1 - DISPUTE_TIMEOUT_NOTARY); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY); // Time since attestation was submitted: AGENT_ROOT_OPTIMISTIC_PERIOD - 1 // Time sinceNotary won the dispute: DISPUTE_TIMEOUT_NOTARY bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should not pass the root assertEq(lightManager.agentRoot(), oldRoot); // Should not clear pending assertTrue(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), newRoot); } function test_passAgentRoot_afterNotaryDisputeTimeout_optimisticPeriodOver() public { bytes32 newRoot = prepareAgentRootDisputeTest(); skip(AGENT_ROOT_OPTIMISTIC_PERIOD - DISPUTE_TIMEOUT_NOTARY); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY); // Time since attestation was submitted: AGENT_ROOT_OPTIMISTIC_PERIOD // Time sinceNotary won the dispute: DISPUTE_TIMEOUT_NOTARY bool rootPending = InterfaceDestination(localDestination()).passAgentRoot(); // Should pass the root assertEq(lightManager.agentRoot(), newRoot); // Should clear pending assertFalse(rootPending); assertEq(InterfaceDestination(localDestination()).nextAgentRoot(), newRoot); } // ═════════════════════════════════════════════════ GAS DATA ══════════════════════════════════════════════════════ function test_getGasData(Random memory random) public { RawSnapshot memory firstSnap; firstSnap.states = new RawState[](2); firstSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: random.nextUint32()}); firstSnap.states[1] = random.nextState({origin: DOMAIN_SYNAPSE, nonce: random.nextUint32()}); RawAttestation memory ra = random.nextAttestation(firstSnap, random.nextUint32()); // Use current agent root in the attestation ra._agentRoot = getAgentRoot(); ra.setDataHash(); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory attPayload, bytes memory attSig) = signAttestation(firstNotary, ra); uint256[] memory firstSnapGas = firstSnap.snapGas(); // Submit first attestation lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, firstSnapGas); uint256 firstSkipTime = random.nextUint32(); skip(firstSkipTime); RawSnapshot memory secondSnap; secondSnap.states = new RawState[](1); secondSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: random.nextUint32()}); vm.assume( GasData.unwrap(firstSnap.states[0].castToState().gasData()) != GasData.unwrap(secondSnap.states[0].castToState().gasData()) ); RawAttestation memory secondRA = random.nextAttestation(secondSnap, random.nextUint32()); address secondNotary = domains[DOMAIN_LOCAL].agents[1]; (attPayload, attSig) = signAttestation(secondNotary, secondRA); uint256[] memory secondSnapGas = secondSnap.snapGas(); // Submit second attestation lightInbox.submitAttestation(attPayload, attSig, secondRA._agentRoot, secondSnapGas); uint256 secondSkipTime = random.nextUint32(); skip(secondSkipTime); // Check getGasData GasData firstRemoteGasData = firstSnap.states[0].castToState().gasData(); GasData firstSynapseGasData = firstSnap.states[1].castToState().gasData(); GasData secondRemoteGasData = secondSnap.states[0].castToState().gasData(); emit log_named_uint("Remote gasData: first", GasData.unwrap(firstRemoteGasData)); emit log_named_uint("Remote gasData: second", GasData.unwrap(secondRemoteGasData)); emit log_named_uint("Synapse gasData: first", GasData.unwrap(firstSynapseGasData)); (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assertEq(GasData.unwrap(gasData), GasData.unwrap(secondRemoteGasData), "!remoteGasData"); assertEq(dataMaturity, secondSkipTime, "!remoteDataMaturity"); (gasData, dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_SYNAPSE); assertEq(GasData.unwrap(gasData), GasData.unwrap(firstSynapseGasData), "!synapseGasData"); assertEq(dataMaturity, firstSkipTime + secondSkipTime, "!synapseDataMaturity"); } function test_getGasData_localDomain(Random memory random) public { RawSnapshot memory firstSnap; firstSnap.states = new RawState[](2); firstSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: random.nextUint32()}); firstSnap.states[1] = random.nextState({origin: localDomain(), nonce: random.nextUint32()}); RawAttestation memory ra = random.nextAttestation(firstSnap, random.nextUint32()); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory attPayload, bytes memory attSig) = signAttestation(firstNotary, ra); uint256[] memory firstSnapGas = firstSnap.snapGas(); // Submit first attestation lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, firstSnapGas); uint256 firstSkipTime = random.nextUint32(); skip(firstSkipTime); // Check getGasData GasData firstRemoteGasData = firstSnap.states[0].castToState().gasData(); (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assertEq(GasData.unwrap(gasData), GasData.unwrap(firstRemoteGasData), "!remoteGasData"); assertEq(dataMaturity, firstSkipTime, "!remoteDataMaturity"); // Should not save data for local domain (gasData, dataMaturity) = InterfaceDestination(localDestination()).getGasData(localDomain()); assertEq(GasData.unwrap(gasData), 0, "!localGasData"); assertEq(dataMaturity, 0, "!localDataMaturity"); } function test_getGasData_noDataForDomain(Random memory random, uint32 domain) public { vm.assume(domain != DOMAIN_REMOTE && domain != DOMAIN_SYNAPSE); RawSnapshot memory firstSnap; firstSnap.states = new RawState[](2); firstSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: random.nextUint32()}); firstSnap.states[1] = random.nextState({origin: DOMAIN_SYNAPSE, nonce: random.nextUint32()}); RawAttestation memory ra = random.nextAttestation(firstSnap, random.nextUint32()); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory attPayload, bytes memory attSig) = signAttestation(firstNotary, ra); uint256[] memory firstSnapGas = firstSnap.snapGas(); // Submit first attestation lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, firstSnapGas); skip(random.nextUint32()); // Check getGasData (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(domain); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); } function prepareGasDataDisputeTest() internal returns (GasData remoteGasData, GasData synapseGasData) { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; Random memory random = Random("salt"); RawSnapshot memory rawSnap = RawSnapshot(new RawState[](2)); rawSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: 1}); rawSnap.states[1] = random.nextState({origin: DOMAIN_SYNAPSE, nonce: 2}); remoteGasData = rawSnap.states[0].castToState().gasData(); synapseGasData = rawSnap.states[1].castToState().gasData(); RawAttestation memory ra = random.nextAttestation({rawSnap: rawSnap, nonce: 1}); uint256[] memory snapGas = rawSnap.snapGas(); (bytes memory attPayload, bytes memory attSig) = signAttestation(notary, ra); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); // Sanity checks { (GasData gasData,) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assert(GasData.unwrap(gasData) == GasData.unwrap(remoteGasData)); } { (GasData gasData,) = InterfaceDestination(localDestination()).getGasData(DOMAIN_SYNAPSE); assert(GasData.unwrap(gasData) == GasData.unwrap(synapseGasData)); } openTestDispute({guardIndex: agentIndex[guard], notaryIndex: agentIndex[notary]}); } function test_getGasData_notaryInDispute() public { prepareGasDataDisputeTest(); skip(7 days); // Check getGasData (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); (gasData, dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_SYNAPSE); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); } function test_getGasData_notaryWonDisputeTimeout() public { prepareGasDataDisputeTest(); skip(7 days); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY - 1); // Check getGasData (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); (gasData, dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_SYNAPSE); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); } function test_getGasData_afterNotaryDisputeTimeout() public { (GasData remoteGasData, GasData synapseGasData) = prepareGasDataDisputeTest(); skip(7 days); prepareNotaryWonDisputeTest(); skip(DISPUTE_TIMEOUT_NOTARY); // Check getGasData (GasData gasData, uint256 dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assertEq(GasData.unwrap(gasData), GasData.unwrap(remoteGasData)); assertEq(dataMaturity, 7 days + DISPUTE_TIMEOUT_NOTARY); (gasData, dataMaturity) = InterfaceDestination(localDestination()).getGasData(DOMAIN_SYNAPSE); assertEq(GasData.unwrap(gasData), GasData.unwrap(synapseGasData)); assertEq(dataMaturity, 7 days + DISPUTE_TIMEOUT_NOTARY); } // ══════════════════════════════════════════════════ HELPERS ══════════════════════════════════════════════════════ /// @notice Prepares execution of the created messages function prepareExecution(SnapshotMock memory sm) public override returns (bytes32 snapRoot, bytes32[] memory snapProof) { RawAttestation memory ra; (ra, snapProof) = createSnapshotProof(sm); RawSnapshot memory rs = fakeSnapshot(sm.rs, sm.rsi); uint256[] memory snapGas = rs.snapGas(); snapRoot = ra.snapRoot; (bytes memory attPayload, bytes memory attSig) = signAttestation(domains[DOMAIN_LOCAL].agent, ra); lightInbox.submitAttestation(attPayload, attSig, ra._agentRoot, snapGas); } /// @notice Returns local domain for the tested contract function localDomain() public pure override returns (uint32) { return DOMAIN_LOCAL; } }