diff --git a/ethereum/contracts/query/QueryResponse.sol b/ethereum/contracts/query/QueryResponse.sol index 263db40cc2..00469750fe 100644 --- a/ethereum/contracts/query/QueryResponse.sol +++ b/ethereum/contracts/query/QueryResponse.sol @@ -6,8 +6,8 @@ pragma solidity ^0.8.0; import "../libraries/external/BytesLib.sol"; import "../interfaces/IWormhole.sol"; -// TODO: move functions to library -contract QueryResponse { +/// @dev QueryResponse is a library that implements the parsing and verification of Cross Chain Query (CCQ) responses. +library QueryResponse { using BytesLib for bytes; /// @dev ParsedQueryResponse is returned by parseAndVerifyQueryResponse(). @@ -27,7 +27,7 @@ contract QueryResponse { bytes response; } - /// @dev ParsedPerChainQueryResponse describes an ETH call per-chain query. + /// @dev EthCallQueryResponse describes an ETH call per-chain query. struct EthCallQueryResponse { bytes requestBlockId; uint64 blockNum; @@ -36,7 +36,7 @@ contract QueryResponse { EthCallData [] result; } - /// @dev ParsedPerChainQueryResponse describes a single ETH call query / response pair. + /// @dev EthCallData describes a single ETH call query / response pair. struct EthCallData { address contractAddress; bytes callData; @@ -45,10 +45,12 @@ contract QueryResponse { bytes public constant responsePrefix = bytes("query_response_0000000000000000000|"); + /// @dev getResponseHash computes the hash of the specified query response. function getResponseHash(bytes memory response) public pure returns (bytes32) { return keccak256(response); } + /// @dev getResponseDigest computes the digest of the specified query response. function getResponseDigest(bytes memory response) public pure returns (bytes32) { return keccak256(abi.encodePacked(responsePrefix,getResponseHash(response))); } @@ -215,3 +217,4 @@ contract QueryResponse { /// If we are here, we've validated the VM is a valid multi-sig that matches the current guardianSet. } } + diff --git a/ethereum/forge-test/query/Query.t.sol b/ethereum/forge-test/query/Query.t.sol index 7824e6f2bd..dba883d02b 100644 --- a/ethereum/forge-test/query/Query.t.sol +++ b/ethereum/forge-test/query/Query.t.sol @@ -21,12 +21,10 @@ contract TestQueryResponse is Test { bytes32 expectedHash = 0xed18e80906ffa80ce953a132a9cbbcf84186955f8fc8ce0322cd68622a58570e; bytes32 expectedDigetst = 0x5b84b19c68ee0b37899230175a92ee6eda4c5192e8bffca1d057d811bb3660e2; - QueryResponse qr; Wormhole wormhole; function setUp() public { - wormhole = deployWormhole(); - qr = new QueryResponse(); + wormhole = deployWormholeForTest(); } uint16 constant TEST_CHAIN_ID = 2; @@ -34,7 +32,7 @@ contract TestQueryResponse is Test { uint16 constant GOVERNANCE_CHAIN_ID = 1; bytes32 constant GOVERNANCE_CONTRACT = 0x0000000000000000000000000000000000000000000000000000000000000004; - function deployWormhole() public returns (Wormhole) { + function deployWormholeForTest() public returns (Wormhole) { // Deploy the Setup contract. Setup setup = new Setup(); @@ -62,25 +60,25 @@ contract TestQueryResponse is Test { } function test_getResponseHash() public { - bytes32 hash = qr.getResponseHash(resp); + bytes32 hash = QueryResponse.getResponseHash(resp); assertEq(hash, expectedHash); } function test_getResponseDigest() public { - bytes32 digest = qr.getResponseDigest(resp); + bytes32 digest = QueryResponse.getResponseDigest(resp); assertEq(digest, expectedDigetst); } function test_verifyQueryResponseSignatures() public view { IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - qr.verifyQueryResponseSignatures(address(wormhole), resp, signatures); + QueryResponse.verifyQueryResponseSignatures(address(wormhole), resp, signatures); } function test_parseAndVerifyQueryResponse() public { IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - QueryResponse.ParsedQueryResponse memory r = qr.parseAndVerifyQueryResponse(address(wormhole), resp, signatures); + QueryResponse.ParsedQueryResponse memory r = QueryResponse.parseAndVerifyQueryResponse(address(wormhole), resp, signatures); assertEq(r.version, 1); assertEq(r.senderChainId, 0); assertEq(r.requestId, hex"ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f200"); @@ -93,6 +91,7 @@ contract TestQueryResponse is Test { } function test_parseEthCallQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. QueryResponse.ParsedPerChainQueryResponse memory r = QueryResponse.ParsedPerChainQueryResponse({ chainId: 5, queryType: 1, @@ -100,7 +99,7 @@ contract TestQueryResponse is Test { response: hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a" }); - QueryResponse.EthCallQueryResponse memory eqr = qr.parseEthCallQueryResponse(r); + QueryResponse.EthCallQueryResponse memory eqr = QueryResponse.parseEthCallQueryResponse(r); assertEq(eqr.requestBlockId, hex"307832613631616334"); assertEq(eqr.blockNum, 44440260); assertEq(eqr.blockHash, hex"c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d04"); @@ -114,6 +113,5 @@ contract TestQueryResponse is Test { assertEq(eqr.result[1].contractAddress, address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270)); assertEq(eqr.result[1].callData, hex"18160ddd"); assertEq(eqr.result[1].result, hex"0000000000000000000000000000000000000000007ae5649beabeddf889364a"); - } } diff --git a/ethereum/scripts/test_query.js b/ethereum/scripts/test_query.js deleted file mode 100644 index 772fd2629a..0000000000 --- a/ethereum/scripts/test_query.js +++ /dev/null @@ -1,88 +0,0 @@ -// run this script with truffle exec -// node_modules/.bin/truffle exec scripts/test_query.js - -const jsonfile = require("jsonfile"); -const QueryResponseABI = jsonfile.readFileSync( - "../build/contracts/QueryResponse.json" -).abi; - -const responseBytes = - "0x010000ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f2000000005301dd9914c6010005010000004600000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd01000501000000b90000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a"; - -const sigBytes = - "ba36cd576a0f9a8a37ec5ea6a174857922f2f170cd7ec62edcbe74b1cc7258d301e8690cfd627e608d63b5d165e2190ba081bb84f5cf473fd353109e152f72fa00"; -const sigs = [ - [ - "0x" + sigBytes.substring(0, 64), - "0x" + sigBytes.substring(64, 128), - "0x" + (parseInt(sigBytes.substring(128, 130), 16) + 27).toString(16), // last byte plus magic 27 - "0x00", - ], -]; -const expectedHash = - "0xed18e80906ffa80ce953a132a9cbbcf84186955f8fc8ce0322cd68622a58570e"; -const expectedDigest = - "0x616674308c1ab1b468665f21fd3808a8fc5807a4ca9859b681d2e3f7ace97cc2"; - -module.exports = async function(callback) { - try { - const QueryResponse = await artifacts.require("QueryResponse"); - //Query deploy - const queryAddress = ( - await QueryResponse.new("0xC89Ce4735882C9F0f0FE26686c53074E09B0D550") - ).address; - - console.log("QueryResponse deployed at: " + queryAddress); - - const initialized = new web3.eth.Contract(QueryResponseABI, queryAddress); - - const hashResult = await initialized.methods - .getResponseHash(responseBytes) - .call(); - console.log("hash:", hashResult); - - const digestResult = await initialized.methods - .getResponseDigest(responseBytes) - .call(); - console.log("digest:", digestResult); - - const verify = await initialized.methods - .verifyQueryResponseSignatures( - "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550", - responseBytes, - sigs - ) - .call(); - console.log("verify result:", verify); - - const response = await initialized.methods - .parseAndVerifyQueryResponse( - "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550", - responseBytes, - sigs - ) - .call(); - console.log("response:", response); - - for (let idx = 0; idx < response.responses.length; ++idx) { - const pcr = response.responses[idx]; - if (pcr.queryType !== "1") { - console.error( - "eth query result" + idx + " has an invalid query type:", - pcr.queryType - ); - } else { - const ethResult = await initialized.methods - .parseEthCallQueryResponse(pcr) - .call(); - console.log("eth query result" + idx + ":", ethResult); - } - } - - console.log("Test complete"); - - callback(); - } catch (e) { - callback(e); - } -};