// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import {DISPUTE_TIMEOUT_NOTARY} from "../../contracts/libs/Constants.sol"; import {CallerNotInbox, DisputeTimeoutNotOver, NotaryInDispute} from "../../contracts/libs/Errors.sol"; import {ChainGas, GasData, InterfaceDestination} from "../../contracts/interfaces/InterfaceDestination.sol"; import {Random} from "../utils/libs/Random.t.sol"; import {RawAttestation, RawSnapshot, RawState} from "../utils/libs/SynapseStructs.t.sol"; import {AgentFlag, AgentStatus, BondingManager, Destination, 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 DestinationSynapseTest is ExecutionHubTest { // Deploy Production version of Destination and Summit and mocks for everything else constructor() SynapseTest(DEPLOY_PROD_DESTINATION_SYNAPSE | DEPLOY_PROD_SUMMIT) {} function test_setupCorrectly() public { Destination dst = Destination(localDestination()); // Check Agents: all Agents are known in BondingManager 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_cleanSetup(Random memory random) public override { uint32 domain = DOMAIN_SYNAPSE; vm.chainId(domain); address caller = random.nextAddress(); BondingManager manager = new BondingManager(domain); address inbox_ = random.nextAddress(); bytes32 agentRoot = random.next(); Destination cleanContract = new Destination(DOMAIN_SYNAPSE, address(manager), inbox_); manager.initialize(address(0), address(cleanContract), inbox_, address(0)); // agentRoot should be ignored on Synapse Chain vm.prank(caller); cleanContract.initialize(agentRoot); assertEq(cleanContract.owner(), caller, "!owner"); assertEq(cleanContract.localDomain(), domain, "!localDomain"); assertEq(cleanContract.agentManager(), address(manager), "!agentManager"); assertEq(cleanContract.inbox(), inbox_, "!inbox"); assertEq(cleanContract.nextAgentRoot(), 0, "!nextAgentRoot"); } function initializeLocalContract() public override { Destination(localContract()).initialize(0); } function test_getAttestation(Random memory random) public { uint256 amount = 10; bytes[] memory attPayloads = new bytes[](amount); uint256 statesAmount = allDomains.length - 1; for (uint32 index = 0; index < amount; ++index) { RawSnapshot memory rs; rs.states = new RawState[](statesAmount); for (uint256 d = 0; d < statesAmount; ++d) { rs.states[d] = random.nextState({origin: allDomains[d + 1], nonce: index + 1}); } RawAttestation memory ra = rs.castToRawAttestation({ agentRoot: getAgentRoot(), nonce: index + 1, blockNumber: uint40(block.number), timestamp: uint40(block.timestamp) }); attPayloads[index] = ra.formatAttestation(); address guard = domains[0].agents[index % DOMAIN_AGENTS]; address notary = domains[DOMAIN_LOCAL].agents[index % DOMAIN_AGENTS]; (bytes memory snapPayload, bytes memory guardSignature) = signSnapshot(guard, rs); (, bytes memory notarySignature) = signSnapshot(notary, rs); inbox.submitSnapshot(snapPayload, guardSignature); inbox.submitSnapshot(snapPayload, notarySignature); skipBlock(); } for (uint32 index = 0; index < amount; ++index) { (bytes memory attPayload, bytes memory attSignature) = InterfaceDestination(localDestination()).getAttestation(index); assertEq(attPayload, attPayloads[index], "!payload"); assertEq(attSignature, "", "!signature"); } } 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_notaryInDispute(uint256 domainId, uint256 notaryId) public { address notary = getNotary(domainId, notaryId); openDispute({guard: domains[0].agent, notary: notary}); vm.prank(address(inbox)); vm.expectRevert(NotaryInDispute.selector); InterfaceDestination(localDestination()).acceptAttestation(agentIndex[notary], 0, "", 0, new ChainGas[](0)); } // ═════════════════════════════════════════════════ 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: 1}); firstSnap.states[1] = random.nextState({origin: DOMAIN_LOCAL, nonce: 1}); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory firstSnapPayload, bytes memory firstNotarySignature) = signSnapshot(firstNotary, firstSnap); (, bytes memory firstGuardSignature) = signSnapshot(domains[0].agent, firstSnap); inbox.submitSnapshot(firstSnapPayload, firstGuardSignature); inbox.submitSnapshot(firstSnapPayload, firstNotarySignature); uint256 firstSkipTime = random.nextUint32(); skip(firstSkipTime); RawSnapshot memory secondSnap; secondSnap.states = new RawState[](1); secondSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: 2}); vm.assume( GasData.unwrap(firstSnap.states[0].castToState().gasData()) != GasData.unwrap(secondSnap.states[0].castToState().gasData()) ); address secondNotary = domains[DOMAIN_REMOTE].agents[0]; (bytes memory secondSnapPayload, bytes memory secondNotarySignature) = signSnapshot(secondNotary, secondSnap); (, bytes memory secondGuardSignature) = signSnapshot(domains[0].agent, secondSnap); inbox.submitSnapshot(secondSnapPayload, secondGuardSignature); inbox.submitSnapshot(secondSnapPayload, secondNotarySignature); uint256 secondSkipTime = random.nextUint32(); skip(secondSkipTime); // Check getGasData GasData firstRemoteGasData = firstSnap.states[0].castToState().gasData(); GasData firstLocalGasData = 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("Local gasData: first", GasData.unwrap(firstLocalGasData)); (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_LOCAL); assertEq(GasData.unwrap(gasData), GasData.unwrap(firstLocalGasData), "!localGasData"); assertEq(dataMaturity, firstSkipTime + secondSkipTime, "!localDataMaturity"); } 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: 1}); firstSnap.states[1] = random.nextState({origin: localDomain(), nonce: 1}); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory firstSnapPayload, bytes memory firstNotarySignature) = signSnapshot(firstNotary, firstSnap); (, bytes memory firstGuardSignature) = signSnapshot(domains[0].agent, firstSnap); inbox.submitSnapshot(firstSnapPayload, firstGuardSignature); inbox.submitSnapshot(firstSnapPayload, firstNotarySignature); 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_LOCAL); RawSnapshot memory firstSnap; firstSnap.states = new RawState[](2); firstSnap.states[0] = random.nextState({origin: DOMAIN_REMOTE, nonce: 1}); firstSnap.states[1] = random.nextState({origin: DOMAIN_LOCAL, nonce: 1}); address firstNotary = domains[DOMAIN_LOCAL].agents[0]; (bytes memory firstSnapPayload, bytes memory firstNotarySignature) = signSnapshot(firstNotary, firstSnap); (, bytes memory firstGuardSignature) = signSnapshot(domains[0].agent, firstSnap); inbox.submitSnapshot(firstSnapPayload, firstGuardSignature); inbox.submitSnapshot(firstSnapPayload, firstNotarySignature); uint256 firstSkipTime = random.nextUint32(); skip(firstSkipTime); // 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 localGasData) { address notary = domains[DOMAIN_LOCAL].agent; address reportGuard = domains[0].agent; address snapshotGuard = domains[0].agents[1]; 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_LOCAL, nonce: 2}); remoteGasData = rawSnap.states[0].castToState().gasData(); localGasData = rawSnap.states[1].castToState().gasData(); // Another Guard signs the snapshot (bytes memory snapPayload, bytes memory guardSignature) = signSnapshot(snapshotGuard, rawSnap); inbox.submitSnapshot(snapPayload, guardSignature); // Notary signs the snapshot (, bytes memory notarySignature) = signSnapshot(notary, rawSnap); inbox.submitSnapshot(snapPayload, notarySignature); // Sanity checks { (GasData gasData,) = InterfaceDestination(localDestination()).getGasData(DOMAIN_REMOTE); assert(GasData.unwrap(gasData) == GasData.unwrap(remoteGasData)); } { (GasData gasData,) = InterfaceDestination(localDestination()).getGasData(DOMAIN_LOCAL); assert(GasData.unwrap(gasData) == GasData.unwrap(localGasData)); } openTestDispute({guardIndex: agentIndex[reportGuard], notaryIndex: agentIndex[notary]}); } function prepareNotaryWonDisputeTest() internal { address notary = domains[DOMAIN_LOCAL].agent; address guard = domains[0].agent; resolveTestDispute({slashedIndex: agentIndex[guard], rivalIndex: 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_LOCAL); 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_LOCAL); assertEq(GasData.unwrap(gasData), 0); assertEq(dataMaturity, 0); } function test_getGasData_afterNotaryDisputeTimeout() public { (GasData remoteGasData, GasData localGasData) = 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_LOCAL); assertEq(GasData.unwrap(gasData), GasData.unwrap(localGasData)); 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); snapRoot = ra.snapRoot; (bytes memory snapPayload, bytes memory guardSignature) = createSignedSnapshot(domains[0].agent, sm.rs, sm.rsi); (, bytes memory notarySignature) = createSignedSnapshot(domains[DOMAIN_LOCAL].agent, sm.rs, sm.rsi); inbox.submitSnapshot(snapPayload, guardSignature); inbox.submitSnapshot(snapPayload, notarySignature); } /// @notice Returns local domain for the tested contract function localDomain() public pure override returns (uint32) { return DOMAIN_SYNAPSE; } }