From 81fe11d887e3a1a92e0cc4ed053cc4ff4571765f Mon Sep 17 00:00:00 2001 From: Dirk Brink Date: Thu, 16 Nov 2023 09:38:12 -0800 Subject: [PATCH] ethereum: Add tests for verifyCurrentQuorum --- ethereum/contracts/interfaces/IWormhole.sol | 2 + ethereum/forge-test/Messages.t.sol | 133 ++++++++++++++++++ .../forge-test/relayer/MockWormhole.sol | 7 + 3 files changed, 142 insertions(+) diff --git a/ethereum/contracts/interfaces/IWormhole.sol b/ethereum/contracts/interfaces/IWormhole.sol index eba9f6f2ea..0984634d11 100644 --- a/ethereum/contracts/interfaces/IWormhole.sol +++ b/ethereum/contracts/interfaces/IWormhole.sol @@ -92,6 +92,8 @@ interface IWormhole { function verifySignatures(bytes32 hash, Signature[] memory signatures, GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason); + function verifyCurrentQuorum(bytes32 hash, Signature[] memory signatures) external view returns (bool valid, string memory response); + function parseVM(bytes memory encodedVM) external pure returns (VM memory vm); function quorum(uint numGuardians) external pure returns (uint numSignaturesRequiredForQuorum); diff --git a/ethereum/forge-test/Messages.t.sol b/ethereum/forge-test/Messages.t.sol index 8b8224dc08..dcc5f1fb8d 100644 --- a/ethereum/forge-test/Messages.t.sol +++ b/ethereum/forge-test/Messages.t.sol @@ -161,4 +161,137 @@ contract TestMessages is Test { assertEq(valid, false); assertEq(reason, "vm.hash doesn't match body"); } + + /// @dev This test checks that quorum is reached in the happy path + function testFuzz_verifyCurrentQuorum(bytes memory encoded) public { + vm.assume(encoded.length > 0); + + // Set the initial guardian set + address[] memory initialGuardians = new address[](1); + initialGuardians[0] = testGuardianPub; + + // Create a guardian set + Structs.GuardianSet memory initialGuardianSet = Structs.GuardianSet({ + keys: initialGuardians, + expirationTime: 0 + }); + + messages.storeGuardianSetPub(initialGuardianSet, uint32(0)); + + bytes32 message = keccak256(encoded); + + // Generate legitimate signature. + Structs.Signature memory goodSignature; + (goodSignature.v, goodSignature.r, goodSignature.s) = vm.sign(testGuardian, message); + assertEq(ecrecover(message, goodSignature.v, goodSignature.r, goodSignature.s), vm.addr(testGuardian)); + goodSignature.guardianIndex = 0; + + // Attempt to verify signatures. + Structs.Signature[] memory sigs = new Structs.Signature[](1); + sigs[0] = goodSignature; + + (bool valid, string memory reason) = messages.verifyCurrentQuorum(message, sigs); + assertEq(valid, true); + assertEq(bytes(reason).length, 0); + } + + /// @dev This test checks that quorum will not be reached if there are no guardians in the current guardian set + function testFuzz_verifyCurrentQuorum_noGuardians(bytes memory encoded) public { + vm.assume(encoded.length > 0); + + bytes32 message = keccak256(encoded); + + // Generate legitimate signature. + Structs.Signature memory goodSignature; + (goodSignature.v, goodSignature.r, goodSignature.s) = vm.sign(testGuardian, message); + assertEq(ecrecover(message, goodSignature.v, goodSignature.r, goodSignature.s), vm.addr(testGuardian)); + goodSignature.guardianIndex = 0; + + // Attempt to verify signatures. + Structs.Signature[] memory sigs = new Structs.Signature[](1); + sigs[0] = goodSignature; + + (bool valid, string memory reason) = messages.verifyCurrentQuorum(message, sigs); + assertEq(valid, false); + assertEq(abi.encodePacked(reason), abi.encodePacked("invalid guardian set")); + } + + /// @dev This test checks that quorum will not be reached if there is >0 invalid signatures in any position + function testFuzz_verifyCurrentQuorum_invalidSignature(bytes memory encoded, uint8 numSigs, uint256 invalidSigIndex) public { + vm.assume(encoded.length > 0); + vm.assume(numSigs != 0); + invalidSigIndex = bound(invalidSigIndex, 0, numSigs - 1); + + // Set the initial guardian set + address[] memory initialGuardians = new address[](numSigs); + + bytes32 message = keccak256(encoded); + + Structs.Signature[] memory signatures = new Structs.Signature[](numSigs); + + for (uint8 i = 0; i < numSigs; ++i) { + (address addr, uint256 key) = makeAddrAndKey(string(abi.encode(i))); + initialGuardians[i] = addr; + + // Create a single invalid signature + if (i == invalidSigIndex) { + (addr, key) = makeAddrAndKey(string(abi.encode(type(uint64).max))); + (signatures[i].v, signatures[i].r, signatures[i].s) = vm.sign(key, message); + signatures[i].guardianIndex = i; + continue; + } + + (signatures[i].v, signatures[i].r, signatures[i].s) = vm.sign(key, message); + signatures[i].guardianIndex = i; + } + + // Create a guardian set + Structs.GuardianSet memory initialGuardianSet = Structs.GuardianSet({ + keys: initialGuardians, + expirationTime: 0 + }); + + messages.storeGuardianSetPub(initialGuardianSet, uint32(0)); + + (bool valid, string memory reason) = messages.verifyCurrentQuorum(message, signatures); + assertEq(valid, false); + assertEq(abi.encodePacked(reason), abi.encodePacked("VM signature invalid")); + } + + /// @dev This test checks that quorum will not be reached with 0); + numGuardians = bound(numGuardians, 1, type(uint8).max); + numSigs = bound(numSigs, 0, messages.quorum(numGuardians) - 1); + + // Set the initial guardian set + address[] memory initialGuardians = new address[](numGuardians); + + bytes32 message = keccak256(encoded); + + Structs.Signature[] memory signatures = new Structs.Signature[](numSigs); + + for (uint8 i = 0; i < numGuardians; ++i) { + (address addr, uint256 key) = makeAddrAndKey(string(abi.encode(i))); + initialGuardians[i] = addr; + + if (i < numSigs) { + (signatures[i].v, signatures[i].r, signatures[i].s) = vm.sign(key, message); + signatures[i].guardianIndex = i; + } + } + + // Create a guardian set + Structs.GuardianSet memory initialGuardianSet = Structs.GuardianSet({ + keys: initialGuardians, + expirationTime: 0 + }); + + messages.storeGuardianSetPub(initialGuardianSet, uint32(0)); + + (bool valid, string memory reason) = messages.verifyCurrentQuorum(message, signatures); + assertEq(valid, false); + assertEq(abi.encodePacked(reason), abi.encodePacked("no quorum")); + } + } diff --git a/relayer/ethereum/forge-test/relayer/MockWormhole.sol b/relayer/ethereum/forge-test/relayer/MockWormhole.sol index 16ef6d6514..98059915a2 100644 --- a/relayer/ethereum/forge-test/relayer/MockWormhole.sol +++ b/relayer/ethereum/forge-test/relayer/MockWormhole.sol @@ -223,6 +223,13 @@ contract MockWormhole is IWormhole { revert("unsupported verifySignatures in wormhole mock"); } + function verifyCurrentQuorum( + bytes32, /*hash*/ + Signature[] memory /*signatures*/ + ) external pure returns (bool /*valid*/, string memory /*reason*/) { + revert("unsupported verifyCurrentQuorum in wormhole mock"); + } + function parseContractUpgrade(bytes memory /*encodedUpgrade*/ ) external pure