diff --git a/ethereum/Makefile b/ethereum/Makefile index daa67bb1c1..d1b72d4eb9 100644 --- a/ethereum/Makefile +++ b/ethereum/Makefile @@ -35,7 +35,7 @@ node_modules: package-lock.json forge_dependencies: lib/forge-std lib/openzeppelin-contracts lib/forge-std: - forge install foundry-rs/forge-std@v1.5.5 --no-git --no-commit + forge install foundry-rs/forge-std@v1.6.1 --no-git --no-commit lib/openzeppelin-contracts: forge install openzeppelin/openzeppelin-contracts@0457042d93d9dfd760dbaa06a4d2f1216fdbe297 --no-git --no-commit diff --git a/ethereum/forge-test/Messages.t.sol b/ethereum/forge-test/Messages.t.sol index ae13be8372..e61a6bfaea 100644 --- a/ethereum/forge-test/Messages.t.sol +++ b/ethereum/forge-test/Messages.t.sol @@ -6,7 +6,50 @@ pragma solidity ^0.8.0; import "../contracts/Messages.sol"; import "../contracts/Setters.sol"; import "../contracts/Structs.sol"; + import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; + +contract WormholeSigner is Test { + // Signer wallet. + struct Wallet { + address addr; + uint256 key; + } + + function encodeAndSignMessage( + Structs.VM memory vm_, + uint256[] memory guardianKeys, + uint32 guardianSetIndex + ) public pure returns (bytes memory signedMessage) { + // Compute the hash of the body + bytes memory body = abi.encodePacked( + vm_.timestamp, + vm_.nonce, + vm_.emitterChainId, + vm_.emitterAddress, + vm_.sequence, + vm_.consistencyLevel, + vm_.payload + ); + vm_.hash = keccak256(abi.encodePacked(keccak256(body))); + + // Sign the hash with the specified guardian private keys. + uint256 guardianCount = guardianKeys.length; + bytes memory signatures = abi.encodePacked(uint8(guardianCount)); + for (uint256 i = 0; i < guardianCount; ++i) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(guardianKeys[i], vm_.hash); + signatures = abi.encodePacked(signatures, uint8(i), r, s, v - 27); + } + + signedMessage = abi.encodePacked( + vm_.version, + guardianSetIndex, + signatures, + body + ); + } +} contract ExportedMessages is Messages, Setters { function storeGuardianSetPub(Structs.GuardianSet memory set, uint32 index) public { @@ -23,9 +66,13 @@ contract TestMessages is Test { uint256 constant testGuardian = 93941733246223705020089879371323733820373732307041878556247502674739205313440; ExportedMessages messages; + WormholeSigner wormholeSimulator; Structs.GuardianSet guardianSet; + + // Guardian set with 19 guardians and wallets with each signing key. Structs.GuardianSet guardianSetOpt; + uint256[] guardianKeys = new uint256[](19); function setupSingleGuardian() internal { // initialize guardian set with one guardian @@ -39,36 +86,49 @@ contract TestMessages is Test { // initialize guardian set with 19 guardians address[] memory keys = new address[](19); for (uint256 i = 0; i < 19; ++i) { - keys[i] = makeAddr(string(abi.encodePacked("guarian", i))); + // create a keypair for each guardian + VmSafe.Wallet memory wallet = vm.createWallet(string(abi.encodePacked("guardian", i))); + keys[i] = wallet.addr; + guardianKeys[i] = wallet.privateKey; } guardianSetOpt = Structs.GuardianSet(keys, 0); - require(messages.quorum(guardianSetOpt.keys.length) == 13, "Quorum should be 13"); + require(messages.quorum(guardianSetOpt.keys.length) == 13, "Quorum should be 13"); } function setUp() public { messages = new ExportedMessages(); + wormholeSimulator = new WormholeSigner(); setupSingleGuardian(); setupMultiGuardian(); - } - - function testParseGuardianSetOptimized(uint8 guardianCount) public view { - vm.assume(guardianCount > 0 && guardianCount <= 19); - - // Encode the guardian set. - bytes memory encodedGuardianSet; - for (uint256 i = 0; i < guardianCount; ++i) { - encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.keys[i]); - } - encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.expirationTime); - - // Parse the guardian set. - Structs.GuardianSet memory parsedSet = messages.parseGuardianSetOptimized(encodedGuardianSet); - - // Validate the results by comparing the parsed set to the original set. - for (uint256 i = 0; i < guardianCount; ++i) { - assert(parsedSet.keys[i] == guardianSetOpt.keys[i]); - } - assert(parsedSet.expirationTime == guardianSetOpt.expirationTime); + } + + function getSignedVM( + bytes memory payload, + bytes32 emitterAddress, + uint16 emitterChainId, + uint256[] memory _guardianKeys, + uint32 guardianSetIndex + ) internal view returns (bytes memory signedTransfer) { + // construct `TransferWithPayload` Wormhole message + Structs.VM memory vm; + + // set the vm values inline + vm.version = uint8(1); + vm.timestamp = uint32(block.timestamp); + vm.emitterChainId = emitterChainId; + vm.emitterAddress = emitterAddress; + vm.sequence = messages.nextSequence( + address(uint160(uint256(emitterAddress))) + ); + vm.consistencyLevel = 15; + vm.payload = payload; + + // encode the bservation + signedTransfer = wormholeSimulator.encodeAndSignMessage( + vm, + _guardianKeys, + guardianSetIndex + ); } function testQuorum() public { @@ -196,4 +256,69 @@ contract TestMessages is Test { assertEq(valid, false); assertEq(reason, "vm.hash doesn't match body"); } + + function testParseGuardianSetOptimized(uint8 guardianCount) public view { + vm.assume(guardianCount > 0 && guardianCount <= 19); + + // Encode the guardian set. + bytes memory encodedGuardianSet; + for (uint256 i = 0; i < guardianCount; ++i) { + encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.keys[i]); + } + encodedGuardianSet = abi.encodePacked(encodedGuardianSet, guardianSetOpt.expirationTime); + + // Parse the guardian set. + Structs.GuardianSet memory parsedSet = messages.parseGuardianSetOptimized(encodedGuardianSet); + + // Validate the results by comparing the parsed set to the original set. + for (uint256 i = 0; i < guardianCount; ++i) { + assert(parsedSet.keys[i] == guardianSetOpt.keys[i]); + } + assert(parsedSet.expirationTime == guardianSetOpt.expirationTime); + } + + function testParseAndVerifyVMOptimized(bytes memory payload) public { + vm.assume(payload.length > 0 && payload.length < 1000); + + uint16 emitterChainId = 16; + bytes32 emitterAddress = bytes32(uint256(uint160(makeAddr("foreignEmitter")))); + uint32 currentSetIndex = messages.getCurrentGuardianSetIndex(); + + // Set the guardian set to the optimized guardian set. + messages.storeGuardianSetPub(guardianSetOpt, currentSetIndex); + messages.setGuardianSetHash(currentSetIndex); + + // Create a message with an arbitrary payload. + bytes memory signedMessage = getSignedVM( + payload, + emitterAddress, + emitterChainId, + guardianKeys, + currentSetIndex + ); + + // Parse and verify the VM. + (Structs.VM memory vm_, bool valid,) = messages.parseAndVerifyVM(signedMessage); + assertEq(valid, true); + + // Parse and verify the VM using the optimized endpoint. + (Structs.VM memory vmOptimized, bool valid_,) = messages.parseAndVerifyVMOptimized( + signedMessage, + messages.getEncodedGuardianSet(currentSetIndex), + currentSetIndex + ); + assertEq(valid_, true); + + // Validate the results by comparing the parsed VM to the optimized VM. + assertEq(vm_.version, vmOptimized.version); + assertEq(vm_.timestamp, vmOptimized.timestamp); + assertEq(vm_.nonce, vmOptimized.nonce); + assertEq(vm_.emitterChainId, vmOptimized.emitterChainId); + assertEq(vm_.emitterAddress, vmOptimized.emitterAddress); + assertEq(vm_.sequence, vmOptimized.sequence); + assertEq(vm_.consistencyLevel, vmOptimized.consistencyLevel); + assertEq(vm_.payload, vmOptimized.payload); + assertEq(vm_.guardianSetIndex, vmOptimized.guardianSetIndex); + assertEq(vm_.signatures.length, vmOptimized.signatures.length); + } }