Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contracts/protocol/switchboard/MessageSwitchboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,10 @@ contract MessageSwitchboard is SwitchboardBase, ReentrancyGuard {
emit TransmitterAssigned(dp.payloadId, params_.newTransmitter);
}

function markIsValid(bytes32 payloadId_, bytes32 digest_) external {
if (attestations[payloadId_][digest_] >= totalWatchers) isValid[payloadId_][digest_] = true;
}

/**
* @notice Sets the transmitter address for payload execution
* @param transmitter_ The new transmitter address
Expand Down
74 changes: 74 additions & 0 deletions test/protocol/switchboard/EVMxSwitchboard.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,80 @@ contract SocketPayloadIdVerificationTest is EVMxSwitchboardTestBase {
evmxSwitchboard.revokeRole(WATCHER_ROLE, grantee);
}

// ============================================
// TESTS - markIsValid Recovery Function
// ============================================

function test_markIsValid_AfterWatcherRevoked() public {
// Setup: Add 2 more watchers (total 3)
addWatchers();
assertEq(evmxSwitchboard.totalWatchers(), 3);

bytes32 digest = keccak256(abi.encode("test payload"));
bytes32 payloadId = bytes32(uint256(0x1234));

// Get 2 watchers to attest (not enough for threshold of 3)
bytes32 signatureDigest = keccak256(
abi.encodePacked(
toBytes32Format(address(evmxSwitchboard)),
CHAIN_SLUG,
payloadId,
digest
)
);

bytes memory signature1 = createSignature(signatureDigest, watcherPrivateKey);
vm.prank(getWatcherAddress());
evmxSwitchboard.attest(payloadId, digest, signature1);

bytes memory signature2 = createSignature(signatureDigest, watcher2PrivateKey);
vm.prank(getWatcher2Address());
evmxSwitchboard.attest(payloadId, digest, signature2);

// Verify: 2 attestations, isValid still false (need 3)
assertEq(evmxSwitchboard.attestations(payloadId, digest), 2);
assertFalse(evmxSwitchboard.isValid(payloadId, digest));

// Revoke watcher3 (totalWatchers becomes 2)
vm.prank(owner);
evmxSwitchboard.revokeWatcherRole(getWatcher3Address());
assertEq(evmxSwitchboard.totalWatchers(), 2);

// isValid is still false even though attestations >= totalWatchers
assertFalse(evmxSwitchboard.isValid(payloadId, digest));

// Call markIsValid to fix the state
evmxSwitchboard.markIsValid(payloadId, digest);

// Now isValid should be true
assertTrue(evmxSwitchboard.isValid(payloadId, digest));
// Attestations count unchanged
assertEq(evmxSwitchboard.attestations(payloadId, digest), 2);
}

function test_markIsValid_NotEnoughAttestations_NoOp() public {
// Setup: 3 watchers
addWatchers();
assertEq(evmxSwitchboard.totalWatchers(), 3);

bytes32 digest = keccak256(abi.encode("test payload"));
bytes32 payloadId = bytes32(uint256(0x1234));

// Only 1 watcher attests
bytes memory signature = _createAttestSignature(payloadId, digest);
vm.prank(getWatcherAddress());
evmxSwitchboard.attest(payloadId, digest, signature);

assertEq(evmxSwitchboard.attestations(payloadId, digest), 1);
assertFalse(evmxSwitchboard.isValid(payloadId, digest));

// Call markIsValid - should be no-op since attestations < totalWatchers
evmxSwitchboard.markIsValid(payloadId, digest);

// isValid should still be false
assertFalse(evmxSwitchboard.isValid(payloadId, digest));
}

// ============================================
// MISSING TESTS - SetRevertingPayload Edge Cases
// ============================================
Expand Down
99 changes: 99 additions & 0 deletions test/protocol/switchboard/MessageSwitchboard.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,105 @@ contract MessageSwitchboardTest is Test, Utils {
assertEq(messageSwitchboard.attestations(payloadId, digest), 1);
}

// ============================================
// TESTS - markIsValid Recovery Function
// ============================================

function test_markIsValid_AfterWatcherRevoked() public {
_setupSiblingConfig();

// Setup: Add 2 more watchers (total 3)
uint256 watcher2Key = 0x2222222222222222222222222222222222222222222222222222222222222222;
uint256 watcher3Key = 0x3333333333333333333333333333333333333333333333333333333333333333;
address watcher2 = vm.addr(watcher2Key);
address watcher3 = vm.addr(watcher3Key);

vm.startPrank(owner);
messageSwitchboard.grantWatcherRole(watcher2);
messageSwitchboard.grantWatcherRole(watcher3);
vm.stopPrank();
assertEq(messageSwitchboard.totalWatchers(), 3);

bytes32 payloadId = bytes32(uint256(0x5678));
DigestParams memory digestParams = _createDigestParams(payloadId, abi.encode("test"));
bytes32 digest = calculateDigest(digestParams);

// Get 2 watchers to attest
bytes32 signatureDigest = keccak256(
abi.encodePacked(
toBytes32Format(address(messageSwitchboard)),
SRC_CHAIN,
payloadId,
digest
)
);

bytes memory signature1 = createSignature(signatureDigest, watcherPrivateKey);
vm.prank(getWatcherAddress());
messageSwitchboard.attest(payloadId, digest, signature1);

bytes memory signature2 = createSignature(signatureDigest, watcher2Key);
vm.prank(watcher2);
messageSwitchboard.attest(payloadId, digest, signature2);

// Verify: 2 attestations, isValid still false
assertEq(messageSwitchboard.attestations(payloadId, digest), 2);
assertFalse(messageSwitchboard.isValid(payloadId, digest));

// Revoke watcher3 (totalWatchers becomes 2)
vm.prank(owner);
messageSwitchboard.revokeWatcherRole(watcher3);
assertEq(messageSwitchboard.totalWatchers(), 2);

// isValid still false even though attestations >= totalWatchers
assertFalse(messageSwitchboard.isValid(payloadId, digest));

// Call markIsValid to fix the state
messageSwitchboard.markIsValid(payloadId, digest);

// Now isValid should be true
assertTrue(messageSwitchboard.isValid(payloadId, digest));
assertEq(messageSwitchboard.attestations(payloadId, digest), 2);
}

function test_markIsValid_NotEnoughAttestations_NoOp() public {
_setupSiblingConfig();

// Setup: 2 watchers total
uint256 watcher2Key = 0x2222222222222222222222222222222222222222222222222222222222222222;
address watcher2 = vm.addr(watcher2Key);

vm.prank(owner);
messageSwitchboard.grantWatcherRole(watcher2);
assertEq(messageSwitchboard.totalWatchers(), 2);

bytes32 payloadId = bytes32(uint256(0x5678));
DigestParams memory digestParams = _createDigestParams(payloadId, abi.encode("test"));
bytes32 digest = calculateDigest(digestParams);

// Only 1 watcher attests
bytes32 signatureDigest = keccak256(
abi.encodePacked(
toBytes32Format(address(messageSwitchboard)),
SRC_CHAIN,
payloadId,
digest
)
);
bytes memory signature = createSignature(signatureDigest, watcherPrivateKey);
vm.prank(getWatcherAddress());
messageSwitchboard.attest(payloadId, digest, signature);

assertEq(messageSwitchboard.attestations(payloadId, digest), 1);
assertFalse(messageSwitchboard.isValid(payloadId, digest));

// Call markIsValid - should be no-op
messageSwitchboard.markIsValid(payloadId, digest);

// isValid should still be false
assertFalse(messageSwitchboard.isValid(payloadId, digest));
}

// ============================================
// IMPORTANT TESTS - GROUP 5: Sponsor Approvals
// ============================================
Expand Down