From ca2dfe3acbd5743a0879b031e09cb7573792d184 Mon Sep 17 00:00:00 2001 From: nonergodic Date: Mon, 2 Sep 2024 13:25:19 -0700 Subject: [PATCH] cleanup, fix, refactor --- src/QueryResponse.sol | 1074 +++++++++---------- src/testing/helpers/QueryTest.sol | 581 +++++------ test/QueryResponse.t.sol | 1604 +++++++++++++++-------------- test/QueryTest.t.sol | 548 +++++----- 4 files changed, 1828 insertions(+), 1979 deletions(-) diff --git a/src/QueryResponse.sol b/src/QueryResponse.sol index b52bb9a..6ce8e17 100644 --- a/src/QueryResponse.sol +++ b/src/QueryResponse.sol @@ -3,122 +3,140 @@ pragma solidity ^0.8.4; import {BytesParsing} from "./libraries/BytesParsing.sol"; -import "./interfaces/IWormhole.sol"; - -// @dev ParsedQueryResponse is returned by QueryResponse.parseAndVerifyQueryResponse(). -struct ParsedQueryResponse { - uint8 version; - uint16 senderChainId; - uint32 nonce; - bytes requestId; // 65 byte sig for off-chain, 32 byte vaaHash for on-chain - ParsedPerChainQueryResponse [] responses; +import {IWormhole} from "./interfaces/IWormhole.sol"; + +error UnsupportedQueryType(uint8 received); + +library QueryType { + //Solidity enums don't permit custom values (i.e. can't start from 1) + //Also invalid enum conversions result in panics and manual range checking requires assembly + // to avoid superfluous double checking. + //So we're sticking with uint8 constants instead. + uint8 internal constant ETH_CALL = 1; + uint8 internal constant ETH_CALL_BY_TIMESTAMP = 2; + uint8 internal constant ETH_CALL_WITH_FINALITY = 3; + uint8 internal constant SOLANA_ACCOUNT = 4; + uint8 internal constant SOLANA_PDA = 5; + + //emulate type(enum).min/max for external consumers (mainly tests) + function min() internal pure returns (uint8) { return ETH_CALL; } + function max() internal pure returns (uint8) { return SOLANA_PDA; } + + function checkValid(uint8 queryType) internal pure { + //slightly more gas efficient than calling `isValid` + if (queryType == 0 || queryType > SOLANA_PDA) + revert UnsupportedQueryType(queryType); + } + + function isValid(uint8 queryType) internal pure returns (bool) { + //see docs/optimizations.md why `< CONST + 1` rather than `<= CONST` + return (queryType > 0 && queryType < SOLANA_PDA + 1); + } +} + +struct QueryResponse { + uint8 version; + uint16 senderChainId; + uint32 nonce; + bytes requestId; // 65 byte sig for off-chain, 32 byte vaaHash for on-chain + PerChainQueryResponse[] responses; } -// @dev ParsedPerChainQueryResponse describes a single per-chain response. -struct ParsedPerChainQueryResponse { - uint16 chainId; - uint8 queryType; - bytes request; - bytes response; +struct PerChainQueryResponse { + uint16 chainId; + uint8 queryType; + bytes request; + bytes response; } -// @dev EthCallQueryResponse describes the response to an ETH call per-chain query. struct EthCallQueryResponse { - bytes requestBlockId; - uint64 blockNum; - uint64 blockTime; - bytes32 blockHash; - EthCallData [] result; + bytes requestBlockId; + uint64 blockNum; + uint64 blockTime; + bytes32 blockHash; + EthCallRecord[] results; } -// @dev EthCallByTimestampQueryResponse describes the response to an ETH call by timestamp per-chain query. struct EthCallByTimestampQueryResponse { - bytes requestTargetBlockIdHint; - bytes requestFollowingBlockIdHint; - uint64 requestTargetTimestamp; - uint64 targetBlockNum; - uint64 targetBlockTime; - uint64 followingBlockNum; - bytes32 targetBlockHash; - bytes32 followingBlockHash; - uint64 followingBlockTime; - EthCallData [] result; + bytes requestTargetBlockIdHint; + bytes requestFollowingBlockIdHint; + uint64 requestTargetTimestamp; + uint64 targetBlockNum; + uint64 targetBlockTime; + uint64 followingBlockNum; + bytes32 targetBlockHash; + bytes32 followingBlockHash; + uint64 followingBlockTime; + EthCallRecord[] results; } -// @dev EthCallWithFinalityQueryResponse describes the response to an ETH call with finality per-chain query. struct EthCallWithFinalityQueryResponse { - bytes requestBlockId; - bytes requestFinality; - uint64 blockNum; - uint64 blockTime; - bytes32 blockHash; - EthCallData [] result; + bytes requestBlockId; + bytes requestFinality; + uint64 blockNum; + uint64 blockTime; + bytes32 blockHash; + EthCallRecord[] results; } -// @dev EthCallData describes a single ETH call query / response pair. -struct EthCallData { - address contractAddress; - bytes callData; - bytes result; +struct EthCallRecord { + address contractAddress; + bytes callData; + bytes result; } -// @dev SolanaAccountQueryResponse describes the response to a Solana Account query per-chain query. struct SolanaAccountQueryResponse { - bytes requestCommitment; - uint64 requestMinContextSlot; - uint64 requestDataSliceOffset; - uint64 requestDataSliceLength; - uint64 slotNumber; - uint64 blockTime; - bytes32 blockHash; - SolanaAccountResult [] results; + bytes requestCommitment; + uint64 requestMinContextSlot; + uint64 requestDataSliceOffset; + uint64 requestDataSliceLength; + uint64 slotNumber; + uint64 blockTime; + bytes32 blockHash; + SolanaAccountResult[] results; } -// @dev SolanaAccountResult describes a single Solana Account query result. struct SolanaAccountResult { - bytes32 account; - uint64 lamports; - uint64 rentEpoch; - bool executable; - bytes32 owner; - bytes data; + bytes32 account; + uint64 lamports; + uint64 rentEpoch; + bool executable; + bytes32 owner; + bytes data; } -// @dev SolanaPdaQueryResponse describes the response to a Solana PDA (Program Derived Address) query per-chain query. struct SolanaPdaQueryResponse { - bytes requestCommitment; - uint64 requestMinContextSlot; - uint64 requestDataSliceOffset; - uint64 requestDataSliceLength; - uint64 slotNumber; - uint64 blockTime; - bytes32 blockHash; - SolanaPdaResult [] results; + bytes requestCommitment; + uint64 requestMinContextSlot; + uint64 requestDataSliceOffset; + uint64 requestDataSliceLength; + uint64 slotNumber; + uint64 blockTime; + bytes32 blockHash; + SolanaPdaResult[] results; } -// @dev SolanaPdaResult describes a single Solana PDA (Program Derived Address) query result. struct SolanaPdaResult { - bytes32 programId; - bytes[] seeds; - bytes32 account; - uint64 lamports; - uint64 rentEpoch; - bool executable; - bytes32 owner; - bytes data; - uint8 bump; + bytes32 programId; + bytes[] seeds; + bytes32 account; + uint64 lamports; + uint64 rentEpoch; + bool executable; + bytes32 owner; + bytes data; + uint8 bump; } // Custom errors -error EmptyWormholeAddress(); + +error WrongQueryType(uint8 received, uint8 expected); error InvalidResponseVersion(); error VersionMismatch(); error ZeroQueries(); error NumberOfResponsesMismatch(); error ChainIdMismatch(); error RequestTypeMismatch(); -error UnsupportedQueryType(uint8 received); -error WrongQueryType(uint8 received, uint8 expected); error UnexpectedNumberOfResults(); error InvalidPayloadLength(uint256 received, uint256 expected); error InvalidContractAddress(); @@ -126,550 +144,434 @@ error InvalidFunctionSignature(); error InvalidChainId(); error StaleBlockNum(); error StaleBlockTime(); - -// @dev QueryResponse is a library that implements the parsing and verification of Cross Chain Query (CCQ) responses. -// For a detailed discussion of these query responses, please see the white paper: -// https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0013_ccq.md -abstract contract QueryResponse { - using BytesParsing for bytes; - - IWormhole public immutable wormhole; - - bytes public constant responsePrefix = bytes("query_response_0000000000000000000|"); - uint8 public constant VERSION = 1; - - // TODO: Consider changing these to an enum. - uint8 public constant QT_ETH_CALL = 1; - uint8 public constant QT_ETH_CALL_BY_TIMESTAMP = 2; - uint8 public constant QT_ETH_CALL_WITH_FINALITY = 3; - uint8 public constant QT_SOL_ACCOUNT = 4; - uint8 public constant QT_SOL_PDA = 5; - uint8 public constant QT_MAX = 6; // Keep this last - - constructor(address _wormhole) { - if (_wormhole == address(0)) { - revert EmptyWormholeAddress(); - } - - wormhole = IWormhole(_wormhole); - } - - /// @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))); - } - - /// @dev parseAndVerifyQueryResponse verifies the query response and returns the parsed response. - function parseAndVerifyQueryResponse(bytes memory response, IWormhole.Signature[] memory signatures) public view returns (ParsedQueryResponse memory r) { - verifyQueryResponseSignatures(response, signatures); - - uint index; - - (r.version, index) = response.asUint8Unchecked(index); - if (r.version != VERSION) { - revert InvalidResponseVersion(); - } - - (r.senderChainId, index) = response.asUint16Unchecked(index); - - // For off chain requests (chainID zero), the requestId is the 65 byte signature. For on chain requests, it is the 32 byte VAA hash. - if (r.senderChainId == 0) { - (r.requestId, index) = response.sliceUnchecked(index, 65); - } else { - (r.requestId, index) = response.sliceUnchecked(index, 32); - } - - uint32 len; - (len, index) = response.asUint32Unchecked(index); // query_request_len - uint reqIdx = index; - - // Scope to avoid stack-too-deep error - { - uint8 version; - (version, reqIdx) = response.asUint8Unchecked(reqIdx); - if (version != r.version) { - revert VersionMismatch(); - } - } - - (r.nonce, reqIdx) = response.asUint32Unchecked(reqIdx); - - uint8 numPerChainQueries; - (numPerChainQueries, reqIdx) = response.asUint8Unchecked(reqIdx); - - // A valid query request has at least one per chain query - if (numPerChainQueries == 0) { - revert ZeroQueries(); - } - - // The response starts after the request. - uint respIdx = index + len; - uint startOfResponse = respIdx; - - uint8 respNumPerChainQueries; - (respNumPerChainQueries, respIdx) = response.asUint8Unchecked(respIdx); - if (respNumPerChainQueries != numPerChainQueries) { - revert NumberOfResponsesMismatch(); - } - - r.responses = new ParsedPerChainQueryResponse[](numPerChainQueries); - - // Walk through the requests and responses in lock step. - for (uint idx; idx < numPerChainQueries;) { - (r.responses[idx].chainId, reqIdx) = response.asUint16Unchecked(reqIdx); - uint16 respChainId; - (respChainId, respIdx) = response.asUint16Unchecked(respIdx); - if (respChainId != r.responses[idx].chainId) { - revert ChainIdMismatch(); - } - - (r.responses[idx].queryType, reqIdx) = response.asUint8Unchecked(reqIdx); - uint8 respQueryType; - (respQueryType, respIdx) = response.asUint8Unchecked(respIdx); - if (respQueryType != r.responses[idx].queryType) { - revert RequestTypeMismatch(); - } - - if (r.responses[idx].queryType < QT_ETH_CALL || r.responses[idx].queryType >= QT_MAX) { - revert UnsupportedQueryType(r.responses[idx].queryType); - } - - (len, reqIdx) = response.asUint32Unchecked(reqIdx); - (r.responses[idx].request, reqIdx) = response.sliceUnchecked(reqIdx, len); - - (len, respIdx) = response.asUint32Unchecked(respIdx); - (r.responses[idx].response, respIdx) = response.sliceUnchecked(respIdx, len); - - unchecked { ++idx; } - } - - // End of request body should align with start of response body - if (startOfResponse != reqIdx) { - revert InvalidPayloadLength(startOfResponse, reqIdx); - } - - checkLength(response, respIdx); - return r; - } - - /// @dev parseEthCallQueryResponse parses a ParsedPerChainQueryResponse for an ETH call per-chain query. - function parseEthCallQueryResponse(ParsedPerChainQueryResponse memory pcr) public pure returns (EthCallQueryResponse memory r) { - if (pcr.queryType != QT_ETH_CALL) { - revert WrongQueryType(pcr.queryType, QT_ETH_CALL); - } - - uint reqIdx; - uint respIdx; - - uint32 len; - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // block_id_len - - (r.requestBlockId, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); - - uint8 numBatchCallData; - (numBatchCallData, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); - - (r.blockNum, respIdx) = pcr.response.asUint64Unchecked(respIdx); - - (r.blockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); - - (r.blockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); - - uint8 respNumResults; - (respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); - if (respNumResults != numBatchCallData) { - revert UnexpectedNumberOfResults(); - } - - r.result = new EthCallData[](numBatchCallData); - - // Walk through the call data and results in lock step. - for (uint idx; idx < numBatchCallData;) { - (r.result[idx].contractAddress, reqIdx) = pcr.request.asAddressUnchecked(reqIdx); - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // call_data_len - (r.result[idx].callData, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); - - (len, respIdx) = pcr.response.asUint32Unchecked(respIdx); // result_len - (r.result[idx].result, respIdx) = pcr.response.sliceUnchecked(respIdx, len); - - unchecked { ++idx; } - } - - checkLength(pcr.request, reqIdx); - checkLength(pcr.response, respIdx); - return r; +error NoQuorum(); +error VerificationFailed(); + +//QueryResponse is a library that implements the parsing and verification of +// Cross Chain Query (CCQ) responses. +// +//For a detailed discussion of these query responses, please see the white paper: +// https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0013_ccq.md +library QueryResponseLib { + using BytesParsing for bytes; + + bytes internal constant RESPONSE_PREFIX = bytes("query_response_0000000000000000000|"); + uint8 internal constant VERSION = 1; + uint64 internal constant MICROSECONDS_PER_SECOND = 1_000_000; + + function calcPrefixedResponseHash(bytes memory response) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(RESPONSE_PREFIX, keccak256(response))); + } + + //WARNING: see verifyQueryResponse WARNING + function parseAndVerifyQueryResponse( + address wormhole, + bytes memory response, + IWormhole.Signature[] memory signatures + ) internal view returns (QueryResponse memory ret) { + verifyQueryResponse(wormhole, response, signatures); + return parseQueryResponse(response); + } + + //WARNING: This call can fail during times of guardian set rotation. + // Unlikely, but possible: + // Since only the current guardian set is considered when verifying signatures here, a response + // will be rejected with a failed verification, even if the signing guardian is still within + // what would otherwise be the 24 hour transition window. + function verifyQueryResponse( + address wormhole, + bytes memory response, + IWormhole.Signature[] memory signatures + ) internal view { unchecked { + IWormhole wormhole_ = IWormhole(wormhole); + IWormhole.GuardianSet memory guardianSet = + wormhole_.getGuardianSet(wormhole_.getCurrentGuardianSetIndex()); + uint quorum = guardianSet.keys.length * 2 / 3 + 1; + if (signatures.length < quorum) + revert NoQuorum(); + + (bool signaturesValid, ) = + wormhole_.verifySignatures(calcPrefixedResponseHash(response), signatures, guardianSet); + if(!signaturesValid) + revert VerificationFailed(); + }} + + function parseQueryResponse( + bytes memory response + ) internal pure returns (QueryResponse memory ret) { unchecked { + uint offset; + + (ret.version, offset) = response.asUint8Unchecked(offset); + if (ret.version != VERSION) + revert InvalidResponseVersion(); + + (ret.senderChainId, offset) = response.asUint16Unchecked(offset); + + //For off chain requests (chainID zero), the requestId is the 65 byte signature. + //For on chain requests, it is the 32 byte VAA hash. + (ret.requestId, offset) = response.sliceUnchecked(offset, ret.senderChainId == 0 ? 65 : 32); + + uint32 queryReqLen; + (queryReqLen, offset) = response.asUint32Unchecked(offset); + uint reqOff = offset; + + { + uint8 version; + (version, reqOff) = response.asUint8Unchecked(reqOff); + if (version != ret.version) + revert VersionMismatch(); } - /// @dev parseEthCallByTimestampQueryResponse parses a ParsedPerChainQueryResponse for an ETH call per-chain query. - function parseEthCallByTimestampQueryResponse(ParsedPerChainQueryResponse memory pcr) public pure returns (EthCallByTimestampQueryResponse memory r) { - if (pcr.queryType != QT_ETH_CALL_BY_TIMESTAMP) { - revert WrongQueryType(pcr.queryType, QT_ETH_CALL_BY_TIMESTAMP); - } - - uint reqIdx; - uint respIdx; - uint32 len; - - (r.requestTargetTimestamp, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request target_time_us - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // Request target_block_id_hint_len - (r.requestTargetBlockIdHint, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request target_block_id_hint - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // following_block_id_hint_len - (r.requestFollowingBlockIdHint, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request following_block_id_hint - - uint8 numBatchCallData; - (numBatchCallData, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); // Request num_batch_call_data + (ret.nonce, reqOff) = response.asUint32Unchecked(reqOff); - (r.targetBlockNum, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response target_block_number - (r.targetBlockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response target_block_hash - (r.targetBlockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response target_block_time_us + uint8 numPerChainQueries; + (numPerChainQueries, reqOff) = response.asUint8Unchecked(reqOff); - (r.followingBlockNum, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response following_block_number - (r.followingBlockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response following_block_hash - (r.followingBlockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response following_block_time_us + //A valid query request has at least one per chain query + if (numPerChainQueries == 0) + revert ZeroQueries(); - uint8 respNumResults; - (respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results - if (respNumResults != numBatchCallData) { - revert UnexpectedNumberOfResults(); - } + //The response starts after the request. + uint respOff = offset + queryReqLen; + uint startOfResponse = respOff; - r.result = new EthCallData[](numBatchCallData); + uint8 respNumPerChainQueries; + (respNumPerChainQueries, respOff) = response.asUint8Unchecked(respOff); + if (respNumPerChainQueries != numPerChainQueries) + revert NumberOfResponsesMismatch(); - // Walk through the call data and results in lock step. - for (uint idx; idx < numBatchCallData;) { - (r.result[idx].contractAddress, reqIdx) = pcr.request.asAddressUnchecked(reqIdx); + ret.responses = new PerChainQueryResponse[](numPerChainQueries); - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // call_data_len - (r.result[idx].callData, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); + //Walk through the requests and responses in lock step. + for (uint i; i < numPerChainQueries; ++i) { + (ret.responses[i].chainId, reqOff) = response.asUint16Unchecked(reqOff); + uint16 respChainId; + (respChainId, respOff) = response.asUint16Unchecked(respOff); + if (respChainId != ret.responses[i].chainId) + revert ChainIdMismatch(); - (len, respIdx) = pcr.response.asUint32Unchecked(respIdx); // result_len - (r.result[idx].result, respIdx) = pcr.response.sliceUnchecked(respIdx, len); + (ret.responses[i].queryType, reqOff) = response.asUint8Unchecked(reqOff); + QueryType.checkValid(ret.responses[i].queryType); + uint8 respQueryType; + (respQueryType, respOff) = response.asUint8Unchecked(respOff); + if (respQueryType != ret.responses[i].queryType) + revert RequestTypeMismatch(); - unchecked { ++idx; } - } + (ret.responses[i].request, reqOff) = response.sliceUint32PrefixedUnchecked(reqOff); - checkLength(pcr.request, reqIdx); - checkLength(pcr.response, respIdx); + (ret.responses[i].response, respOff) = response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev parseEthCallWithFinalityQueryResponse parses a ParsedPerChainQueryResponse for an ETH call per-chain query. - function parseEthCallWithFinalityQueryResponse(ParsedPerChainQueryResponse memory pcr) public pure returns (EthCallWithFinalityQueryResponse memory r) { - if (pcr.queryType != QT_ETH_CALL_WITH_FINALITY) { - revert WrongQueryType(pcr.queryType, QT_ETH_CALL_WITH_FINALITY); - } - - uint reqIdx; - uint respIdx; - uint32 len; - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // Request block_id_len - (r.requestBlockId, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request block_id - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // Request finality_len - (r.requestFinality, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request finality - - uint8 numBatchCallData; - (numBatchCallData, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); // Request num_batch_call_data + //End of request body should align with start of response body + if (startOfResponse != reqOff) + revert InvalidPayloadLength(startOfResponse, reqOff); - (r.blockNum, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_number + checkLength(response, respOff); + return ret; + }} - (r.blockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response block_hash + function parseEthCallQueryResponse( + PerChainQueryResponse memory pcr + ) internal pure returns (EthCallQueryResponse memory ret) { unchecked { + if (pcr.queryType != QueryType.ETH_CALL) + revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL); - (r.blockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_time_us + uint reqOff; + uint respOff; - uint8 respNumResults; - (respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results - if (respNumResults != numBatchCallData) { - revert UnexpectedNumberOfResults(); - } + uint8 numBatchCallData; + (ret.requestBlockId, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (numBatchCallData, reqOff) = pcr.request.asUint8Unchecked(reqOff); - r.result = new EthCallData[](numBatchCallData); + uint8 respNumResults; + (ret.blockNum, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.blockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (respNumResults, respOff) = pcr.response.asUint8Unchecked(respOff); - // Walk through the call data and results in lock step. - for (uint idx; idx < numBatchCallData;) { - (r.result[idx].contractAddress, reqIdx) = pcr.request.asAddressUnchecked(reqIdx); + if (respNumResults != numBatchCallData) + revert UnexpectedNumberOfResults(); - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // call_data_len - (r.result[idx].callData, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); + ret.results = new EthCallRecord[](numBatchCallData); - (len, respIdx) = pcr.response.asUint32Unchecked(respIdx); // result_len - (r.result[idx].result, respIdx) = pcr.response.sliceUnchecked(respIdx, len); + //Walk through the call inputs and outputs in lock step. + for (uint i; i < numBatchCallData; ++i) { + (ret.results[i].contractAddress, reqOff) = pcr.request.asAddressUnchecked(reqOff); + (ret.results[i].callData, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); - unchecked { ++idx; } - } - - checkLength(pcr.request, reqIdx); - checkLength(pcr.response, respIdx); + (ret.results[i].result, respOff) = pcr.response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev parseSolanaAccountQueryResponse parses a ParsedPerChainQueryResponse for a Solana Account per-chain query. - function parseSolanaAccountQueryResponse(ParsedPerChainQueryResponse memory pcr) public pure returns (SolanaAccountQueryResponse memory r) { - if (pcr.queryType != QT_SOL_ACCOUNT) { - revert WrongQueryType(pcr.queryType, QT_SOL_ACCOUNT); - } - - uint reqIdx; - uint respIdx; - uint32 len; - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // Request commitment_len - (r.requestCommitment, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request commitment - (r.requestMinContextSlot, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request min_context_slot - (r.requestDataSliceOffset, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request data_slice_offset - (r.requestDataSliceLength, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request data_slice_length - - uint8 numAccounts; - (numAccounts, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); // Request num_accounts - - (r.slotNumber, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response slot_number - (r.blockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_time_us - (r.blockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response block_hash - - uint8 respNumResults; - (respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results - if (respNumResults != numAccounts) { - revert UnexpectedNumberOfResults(); - } - - r.results = new SolanaAccountResult[](numAccounts); - - // Walk through the call data and results in lock step. - for (uint idx; idx < numAccounts;) { - (r.results[idx].account, reqIdx) = pcr.request.asBytes32Unchecked(reqIdx); // Request account - - (r.results[idx].lamports, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response lamports - (r.results[idx].rentEpoch, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response rent_epoch - - (r.results[idx].executable, respIdx) = pcr.response.asBoolUnchecked(respIdx); // Response executable - - (r.results[idx].owner, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response owner - - - (len, respIdx) = pcr.response.asUint32Unchecked(respIdx); // result_len - (r.results[idx].data, respIdx) = pcr.response.sliceUnchecked(respIdx, len); - - unchecked { ++idx; } - } - - checkLength(pcr.request, reqIdx); - checkLength(pcr.response, respIdx); + checkLength(pcr.request, reqOff); + checkLength(pcr.response, respOff); + return ret; + }} + + function parseEthCallByTimestampQueryResponse( + PerChainQueryResponse memory pcr + ) internal pure returns (EthCallByTimestampQueryResponse memory ret) { unchecked { + if (pcr.queryType != QueryType.ETH_CALL_BY_TIMESTAMP) + revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL_BY_TIMESTAMP); + + uint reqOff; + uint respOff; + + uint8 numBatchCallData; + (ret.requestTargetTimestamp, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (ret.requestTargetBlockIdHint, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (ret.requestFollowingBlockIdHint, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (numBatchCallData, reqOff) = pcr.request.asUint8Unchecked(reqOff); + + uint8 respNumResults; + (ret.targetBlockNum, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.targetBlockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.targetBlockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.followingBlockNum, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.followingBlockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.followingBlockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (respNumResults, respOff) = pcr.response.asUint8Unchecked(respOff); + + if (respNumResults != numBatchCallData) + revert UnexpectedNumberOfResults(); + + ret.results = new EthCallRecord[](numBatchCallData); + + // Walk through the call inputs and outputs in lock step. + for (uint i; i < numBatchCallData; ++i) { + (ret.results[i].contractAddress, reqOff) = pcr.request.asAddressUnchecked(reqOff); + (ret.results[i].callData, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + + (ret.results[i].result, respOff) = pcr.response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev parseSolanaPdaQueryResponse parses a ParsedPerChainQueryResponse for a Solana Pda per-chain query. - function parseSolanaPdaQueryResponse(ParsedPerChainQueryResponse memory pcr) public pure returns (SolanaPdaQueryResponse memory r) { - if (pcr.queryType != QT_SOL_PDA) { - revert WrongQueryType(pcr.queryType, QT_SOL_PDA); - } - - uint reqIdx; - uint respIdx; - uint32 len; - - (len, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); // Request commitment_len - (r.requestCommitment, reqIdx) = pcr.request.sliceUnchecked(reqIdx, len); // Request commitment - (r.requestMinContextSlot, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request min_context_slot - (r.requestDataSliceOffset, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request data_slice_offset - (r.requestDataSliceLength, reqIdx) = pcr.request.asUint64Unchecked(reqIdx); // Request data_slice_length - - uint8 numPdas; - (numPdas, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); // Request num_Pdas + checkLength(pcr.request, reqOff); + checkLength(pcr.response, respOff); + }} - (r.slotNumber, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response slot_number - (r.blockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_time_us - (r.blockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response block_hash + function parseEthCallWithFinalityQueryResponse( + PerChainQueryResponse memory pcr + ) internal pure returns (EthCallWithFinalityQueryResponse memory ret) { unchecked { + if (pcr.queryType != QueryType.ETH_CALL_WITH_FINALITY) + revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL_WITH_FINALITY); - uint8 respNumResults; - (respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results - if (respNumResults != numPdas) { - revert UnexpectedNumberOfResults(); - } + uint reqOff; + uint respOff; - r.results = new SolanaPdaResult[](numPdas); + uint8 numBatchCallData; + (ret.requestBlockId, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (ret.requestFinality, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (numBatchCallData, reqOff) = pcr.request.asUint8Unchecked(reqOff); - // Walk through the call data and results in lock step. - for (uint idx; idx < numPdas;) { - (r.results[idx].programId, reqIdx) = pcr.request.asBytes32Unchecked(reqIdx); // Request programId + uint8 respNumResults; + (ret.blockNum, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.blockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (respNumResults, respOff) = pcr.response.asUint8Unchecked(respOff); - uint8 numSeeds; // Request number of seeds - (numSeeds, reqIdx) = pcr.request.asUint8Unchecked(reqIdx); - r.results[idx].seeds = new bytes[](numSeeds); - for (uint idx2; idx2 < numSeeds;) { - uint32 seedLen; - (seedLen, reqIdx) = pcr.request.asUint32Unchecked(reqIdx); - (r.results[idx].seeds[idx2], reqIdx) = pcr.request.sliceUnchecked(reqIdx, seedLen); - unchecked { ++idx2; } - } + if (respNumResults != numBatchCallData) + revert UnexpectedNumberOfResults(); - (r.results[idx].account, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response account - (r.results[idx].bump, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response bump + ret.results = new EthCallRecord[](numBatchCallData); - (r.results[idx].lamports, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response lamports - (r.results[idx].rentEpoch, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response rent_epoch + //Walk through the call inputs and outputs in lock step. + for (uint i; i < numBatchCallData; ++i) { + (ret.results[i].contractAddress, reqOff) = pcr.request.asAddressUnchecked(reqOff); + (ret.results[i].callData, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); - (r.results[idx].executable, respIdx) = pcr.response.asBoolUnchecked(respIdx); // Response executable - - (r.results[idx].owner, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response owner - - (len, respIdx) = pcr.response.asUint32Unchecked(respIdx); // result_len - (r.results[idx].data, respIdx) = pcr.response.sliceUnchecked(respIdx, len); - - unchecked { ++idx; } - } - - checkLength(pcr.request, reqIdx); - checkLength(pcr.response, respIdx); + (ret.results[i].result, respOff) = pcr.response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev validateBlockTime validates that the parsed block time isn't stale - /// @param _blockTime Wormhole block time in MICROseconds - /// @param _minBlockTime Minium block time in seconds - function validateBlockTime(uint64 _blockTime, uint256 _minBlockTime) public pure { - uint256 blockTimeInSeconds = _blockTime / 1_000_000; // Rounds down - - if (blockTimeInSeconds < _minBlockTime) { - revert StaleBlockTime(); - } + checkLength(pcr.request, reqOff); + checkLength(pcr.response, respOff); + }} + + function parseSolanaAccountQueryResponse( + PerChainQueryResponse memory pcr + ) internal pure returns (SolanaAccountQueryResponse memory ret) { unchecked { + if (pcr.queryType != QueryType.SOLANA_ACCOUNT) + revert WrongQueryType(pcr.queryType, QueryType.SOLANA_ACCOUNT); + + uint reqOff; + uint respOff; + + uint8 numAccounts; + (ret.requestCommitment, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (ret.requestMinContextSlot, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (ret.requestDataSliceOffset, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (ret.requestDataSliceLength, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (numAccounts, reqOff) = pcr.request.asUint8Unchecked(reqOff); + + uint8 respNumResults; + (ret.slotNumber, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + (respNumResults, respOff) = pcr.response.asUint8Unchecked(respOff); + + if (respNumResults != numAccounts) + revert UnexpectedNumberOfResults(); + + ret.results = new SolanaAccountResult[](numAccounts); + + //Walk through the call inputs and outputs in lock step. + for (uint i; i < numAccounts; ++i) { + (ret.results[i].account, reqOff) = pcr.request.asBytes32Unchecked(reqOff); + + (ret.results[i].lamports, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.results[i].rentEpoch, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.results[i].executable, respOff) = pcr.response.asBoolUnchecked(respOff); + (ret.results[i].owner, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.results[i].data, respOff) = pcr.response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev validateBlockNum validates that the parsed blockNum isn't stale - function validateBlockNum(uint64 _blockNum, uint256 _minBlockNum) public pure { - if (_blockNum < _minBlockNum) { - revert StaleBlockNum(); - } - } - - /// @dev validateChainId validates that the parsed chainId is one of an array of chainIds we expect - function validateChainId(uint16 chainId, uint16[] memory _validChainIds) public pure { - bool validChainId = false; - - uint256 numChainIds = _validChainIds.length; - - for (uint256 idx; idx < numChainIds;) { - if (chainId == _validChainIds[idx]) { - validChainId = true; - break; - } - - unchecked { ++idx; } - } - - if (!validChainId) revert InvalidChainId(); - } - - /// @dev validateMutlipleEthCallData validates that each EthCallData in an array comes from a function signature and contract address we expect - function validateMultipleEthCallData(EthCallData[] memory r, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public pure { - uint256 callDatasLength = r.length; - - for (uint256 idx; idx < callDatasLength;) { - validateEthCallData(r[idx], _expectedContractAddresses, _expectedFunctionSignatures); - - unchecked { ++idx; } - } + checkLength(pcr.request, reqOff); + checkLength(pcr.response, respOff); + }} + + function parseSolanaPdaQueryResponse( + PerChainQueryResponse memory pcr + ) internal pure returns (SolanaPdaQueryResponse memory ret) { unchecked { + if (pcr.queryType != QueryType.SOLANA_PDA) + revert WrongQueryType(pcr.queryType, QueryType.SOLANA_PDA); + + uint reqOff; + uint respOff; + + (ret.requestCommitment, reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + (ret.requestMinContextSlot, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (ret.requestDataSliceOffset, reqOff) = pcr.request.asUint64Unchecked(reqOff); + (ret.requestDataSliceLength, reqOff) = pcr.request.asUint64Unchecked(reqOff); + + uint8 numPdas; + (numPdas, reqOff) = pcr.request.asUint8Unchecked(reqOff); + + (ret.slotNumber, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockTime, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.blockHash, respOff) = pcr.response.asBytes32Unchecked(respOff); + + uint8 respNumResults; + (respNumResults, respOff) = pcr.response.asUint8Unchecked(respOff); + if (respNumResults != numPdas) + revert UnexpectedNumberOfResults(); + + ret.results = new SolanaPdaResult[](numPdas); + + //Walk through the call inputs and outputs in lock step. + for (uint i; i < numPdas; ++i) { + (ret.results[i].programId, reqOff) = pcr.request.asBytes32Unchecked(reqOff); + + uint8 reqNumSeeds; + (reqNumSeeds, reqOff) = pcr.request.asUint8Unchecked(reqOff); + ret.results[i].seeds = new bytes[](reqNumSeeds); + for (uint s; s < reqNumSeeds; ++s) + (ret.results[i].seeds[s], reqOff) = pcr.request.sliceUint32PrefixedUnchecked(reqOff); + + (ret.results[i].account, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.results[i].bump, respOff) = pcr.response.asUint8Unchecked(respOff); + (ret.results[i].lamports, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.results[i].rentEpoch, respOff) = pcr.response.asUint64Unchecked(respOff); + (ret.results[i].executable, respOff) = pcr.response.asBoolUnchecked(respOff); + (ret.results[i].owner, respOff) = pcr.response.asBytes32Unchecked(respOff); + (ret.results[i].data, respOff) = pcr.response.sliceUint32PrefixedUnchecked(respOff); } - /// @dev validateEthCallData validates that EthCallData comes from a function signature and contract address we expect - /// @dev An empty array means we accept all addresses/function signatures - /// @dev Example 1: To accept signatures 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)` you'd pass in [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd)] - /// @dev Example 2: To accept any function signatures from `address(abcd)` or `address(efab)` you'd pass in [], [address(abcd), address(efab)] - /// @dev Example 3: To accept function signature 0xaaaaaaaa from any address you'd pass in [0xaaaaaaaa], [] - /// @dev WARNING Example 4: If you want to accept signature 0xaaaaaaaa from `address(abcd)` and signature 0xbbbbbbbb from `address(efab)` the following input would be incorrect: - /// @dev [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd), address(efab)] - /// @dev This would accept both 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)` AND `address(efab)`. Instead you should make 2 calls to this method - /// @dev using the pattern in Example 1. [0xaaaaaaaa], [address(abcd)] OR [0xbbbbbbbb], [address(efab)] - function validateEthCallData(EthCallData memory r, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public pure { - bool validContractAddress = _expectedContractAddresses.length == 0 ? true : false; - bool validFunctionSignature = _expectedFunctionSignatures.length == 0 ? true : false; - - uint256 contractAddressesLength = _expectedContractAddresses.length; - - // Check that the contract address called in the request is expected - for (uint256 idx; idx < contractAddressesLength;) { - if (r.contractAddress == _expectedContractAddresses[idx]) { - validContractAddress = true; - break; - } - - unchecked { ++idx; } - } - - // Early exit to save gas - if (!validContractAddress) { - revert InvalidContractAddress(); - } - - uint256 functionSignaturesLength = _expectedFunctionSignatures.length; - - // Check that the function signature called is expected - for (uint256 idx; idx < functionSignaturesLength;) { - (bytes4 funcSig,) = r.callData.asBytes4Unchecked(0); - if (funcSig == _expectedFunctionSignatures[idx]) { - validFunctionSignature = true; - break; - } - - unchecked { ++idx; } - } - - if (!validFunctionSignature) { - revert InvalidFunctionSignature(); - } - } - - /** - * @dev verifyQueryResponseSignatures verifies the signatures on a query response. It calls into the Wormhole contract. - * IWormhole.Signature expects the last byte to be bumped by 27 - * see https://github.com/wormhole-foundation/wormhole/blob/637b1ee657de7de05f783cbb2078dd7d8bfda4d0/ethereum/contracts/Messages.sol#L174 - */ - function verifyQueryResponseSignatures(bytes memory response, IWormhole.Signature[] memory signatures) public view { - // It might be worth adding a verifyCurrentQuorum call on the core bridge so that there is only 1 cross call instead of 4. - uint32 gsi = wormhole.getCurrentGuardianSetIndex(); - IWormhole.GuardianSet memory guardianSet = wormhole.getGuardianSet(gsi); - - bytes32 responseHash = getResponseDigest(response); - - /** - * @dev Checks whether the guardianSet has zero keys - * WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure - * that guardianSet key size doesn't fall to zero and negatively impact quorum assessment. If guardianSet - * key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and - * signature verification. - */ - if(guardianSet.keys.length == 0){ - revert("invalid guardian set"); - } - - /** - * @dev We're using a fixed point number transformation with 1 decimal to deal with rounding. - * WARNING: This quorum check is critical to assessing whether we have enough Guardian signatures to validate a VM - * if making any changes to this, obtain additional peer review. If guardianSet key length is 0 and - * vm.signatures length is 0, this could compromise the integrity of both vm and signature verification. - */ - if (signatures.length < wormhole.quorum(guardianSet.keys.length)){ - revert("no quorum"); - } - - /// @dev Verify the proposed vm.signatures against the guardianSet - (bool signaturesValid, string memory invalidReason) = wormhole.verifySignatures(responseHash, signatures, guardianSet); - if(!signaturesValid){ - revert(invalidReason); - } - - /// If we are here, we've validated the VM is a valid multi-sig that matches the current guardianSet. - } - - /// @dev checkLength verifies that the message was fully consumed. - function checkLength(bytes memory encoded, uint256 expected) private pure { - if (encoded.length != expected) { - revert InvalidPayloadLength(encoded.length, expected); - } + checkLength(pcr.request, reqOff); + checkLength(pcr.response, respOff); + }} + + function validateBlockTime( + uint64 blockTimeInMicroSeconds, + uint256 minBlockTimeInSeconds + ) internal pure { + uint256 blockTimeInSeconds = blockTimeInMicroSeconds / MICROSECONDS_PER_SECOND; // Rounds down + + if (blockTimeInSeconds < minBlockTimeInSeconds) + revert StaleBlockTime(); + } + + function validateBlockNum(uint64 blockNum, uint256 minBlockNum) internal pure { + if (blockNum < minBlockNum) + revert StaleBlockNum(); + } + + function validateChainId( + uint16 chainId, + uint16[] memory validChainIds + ) internal pure { unchecked { + uint len = validChainIds.length; + for (uint i; i < len; ++i) + if (chainId == validChainIds[i]) + return; + + revert InvalidChainId(); + }} + + function validateEthCallRecord( + EthCallRecord[] memory ecrs, + address[] memory validContractAddresses, + bytes4[] memory validFunctionSignatures + ) internal pure { unchecked { + uint len = ecrs.length; + for (uint i; i < len; ++i) + validateEthCallRecord(ecrs[i], validContractAddresses, validFunctionSignatures); + }} + + //validates that EthCallRecord a valid function signature and contract address + //An empty array means we accept all addresses/function signatures + // Example 1: To accept signatures 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)` + // you'd pass in [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd)] + // Example 2: To accept any function signatures from `address(abcd)` or `address(efab)` + // you'd pass in [], [address(abcd), address(efab)] + // Example 3: To accept function signature 0xaaaaaaaa from any address + // you'd pass in [0xaaaaaaaa], [] + // + // WARNING Example 4: If you want to accept signature 0xaaaaaaaa from `address(abcd)` + // and signature 0xbbbbbbbb from `address(efab)` the following input would be incorrect: + // [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd), address(efab)] + // This would accept both 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)` AND `address(efab)`. + // Instead you should make 2 calls to this method using the pattern in Example 1. + // [0xaaaaaaaa], [address(abcd)] OR [0xbbbbbbbb], [address(efab)] + function validateEthCallRecord( + EthCallRecord memory ecd, + address[] memory validContractAddresses, //empty array means accept all + bytes4[] memory validFunctionSignatures //empty array means accept all + ) internal pure { unchecked { + if (validContractAddresses.length > 0) + validateContractAddress(ecd.contractAddress, validContractAddresses); + + if (validFunctionSignatures.length > 0) { + (bytes4 funcSig,) = ecd.callData.asBytes4(0); + validateFunctionSignature(funcSig, validFunctionSignatures); } + }} + + function validateContractAddress( + address contractAddress, + address[] memory validContractAddresses + ) private pure { unchecked { + uint len = validContractAddresses.length; + for (uint i; i < len; ++i) + if (contractAddress == validContractAddresses[i]) + return; + + revert InvalidContractAddress(); + }} + + function validateFunctionSignature( + bytes4 functionSignature, + bytes4[] memory validFunctionSignatures + ) private pure { unchecked { + uint len = validFunctionSignatures.length; + for (uint i; i < len; ++i) + if (functionSignature == validFunctionSignatures[i]) + return; + + revert InvalidFunctionSignature(); + }} + + //we use this over BytesParsing.checkLength to return our custom errors in all error cases + function checkLength(bytes memory encoded, uint256 expected) private pure { + if (encoded.length != expected) + revert InvalidPayloadLength(encoded.length, expected); + } } diff --git a/src/testing/helpers/QueryTest.sol b/src/testing/helpers/QueryTest.sol index 7005d31..204dc67 100644 --- a/src/testing/helpers/QueryTest.sol +++ b/src/testing/helpers/QueryTest.sol @@ -2,351 +2,270 @@ pragma solidity ^0.8.4; -// @dev QueryTest is a library to build Cross Chain Query (CCQ) responses for testing purposes. +//build Cross Chain Query (CCQ) responses for testing purposes. library QueryTest { - // Custom errors - error SolanaTooManyPDAs(); - error SolanaTooManySeeds(); - error SolanaSeedTooLong(); - - // - // Query Request stuff - // - - /// @dev buildOffChainQueryRequestBytes builds an off chain query request from the specified fields. - function buildOffChainQueryRequestBytes( - uint8 _version, - uint32 _nonce, - uint8 _numPerChainQueries, - bytes memory _perChainQueries - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _version, - _nonce, - _numPerChainQueries, - _perChainQueries // Each created by buildPerChainRequestBytes() - ); - } + error SolanaTooManyPDAs(); + error SolanaTooManySeeds(); + error SolanaSeedTooLong(); - /// @dev buildPerChainRequestBytes builds a per chain request from the specified fields. - function buildPerChainRequestBytes( - uint16 _chainId, - uint8 _queryType, - bytes memory _queryBytes - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _chainId, - _queryType, - uint32(_queryBytes.length), - _queryBytes - ); - } + //According to the spec, there may be at most 16 seeds. + // https://github.com/gagliardetto/solana-go/blob/6fe3aea02e3660d620433444df033fc3fe6e64c1/keys.go#L559 + uint public constant SOLANA_MAX_SEEDS = 16; - /// @dev buildEthCallRequestBytes builds an eth_call query request from the specified fields. - function buildEthCallRequestBytes( - bytes memory _blockId, - uint8 _numCallData, - bytes memory _callData // Created with buildEthCallDataBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - uint32(_blockId.length), - _blockId, - _numCallData, - _callData - ); - } + //According to the spec, a seed may be at most 32 bytes. + // https://github.com/gagliardetto/solana-go/blob/6fe3aea02e3660d620433444df033fc3fe6e64c1/keys.go#L557 + uint public constant SOLANA_MAX_SEED_LEN = 32; - /// @dev buildEthCallByTimestampRequestBytes builds an eth_call_by_timestamp query request from the specified fields. - function buildEthCallByTimestampRequestBytes( - uint64 _targetTimeUs, - bytes memory _targetBlockHint, - bytes memory _followingBlockHint, - uint8 _numCallData, - bytes memory _callData // Created with buildEthCallDataBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _targetTimeUs, - uint32(_targetBlockHint.length), - _targetBlockHint, - uint32(_followingBlockHint.length), - _followingBlockHint, - _numCallData, - _callData - ); - } + // --- Query Request --- - /// @dev buildEthCallWithFinalityRequestBytes builds an eth_call_with_finality query request from the specified fields. - function buildEthCallWithFinalityRequestBytes( - bytes memory _blockId, - bytes memory _finality, - uint8 _numCallData, - bytes memory _callData // Created with buildEthCallDataBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - uint32(_blockId.length), - _blockId, - uint32(_finality.length), - _finality, - _numCallData, - _callData - ); - } + function buildOffChainQueryRequestBytes( + uint8 version, + uint32 nonce, + uint8 numPerChainQueries, + bytes memory perChainQueries + ) internal pure returns (bytes memory){ + return abi.encodePacked( + version, + nonce, + numPerChainQueries, + perChainQueries // Each created by buildPerChainRequestBytes() + ); + } - /// @dev buildEthCallDataBytes builds the call data associated with one of the eth_call family of queries. - function buildEthCallDataBytes( - address _contractAddress, - bytes memory _callData - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _contractAddress, - uint32(_callData.length), - _callData - ); - } - - /// @dev buildSolanaAccountRequestBytes builds a sol_account query request from the specified fields. - function buildSolanaAccountRequestBytes( - bytes memory _commitment, - uint64 _minContextSlot, - uint64 _dataSliceOffset, - uint64 _dataSliceLength, - uint8 _numAccounts, - bytes memory _accounts // Each account is 32 bytes. - ) internal pure returns (bytes memory){ - return abi.encodePacked( - uint32(_commitment.length), - _commitment, - _minContextSlot, - _dataSliceOffset, - _dataSliceLength, - _numAccounts, - _accounts - ); - } - - /// @dev buildSolanaPdaRequestBytes builds a sol_pda query request from the specified fields. - function buildSolanaPdaRequestBytes( - bytes memory _commitment, - uint64 _minContextSlot, - uint64 _dataSliceOffset, - uint64 _dataSliceLength, - bytes[] memory _pdas // Created with multiple calls to buildSolanaPdaEntry() - ) internal pure returns (bytes memory){ - uint numPdas = _pdas.length; - if (numPdas > 255) { - revert SolanaTooManyPDAs(); - } - bytes memory result = abi.encodePacked( - uint32(_commitment.length), - _commitment, - _minContextSlot, - _dataSliceOffset, - _dataSliceLength, - uint8(numPdas) - ); - - for (uint idx; idx < numPdas;) { - result = abi.encodePacked( - result, - _pdas[idx] - ); - - unchecked { ++idx; } - } - - return result; - } + function buildPerChainRequestBytes( + uint16 chainId, + uint8 queryType, + bytes memory queryBytes + ) internal pure returns (bytes memory){ + return abi.encodePacked(chainId, queryType, uint32(queryBytes.length), queryBytes); + } - /// @dev buildSolanaPdaEntry builds a PDA entry for a sol_pda query. - function buildSolanaPdaEntry( - bytes32 _programId, - uint8 _numSeeds, - bytes memory _seeds // Created with buildSolanaPdaSeedBytes() - ) internal pure returns (bytes memory){ - if (_numSeeds > SolanaMaxSeeds) { - revert SolanaTooManySeeds(); - } - return abi.encodePacked( - _programId, - _numSeeds, - _seeds - ); - } + function buildEthCallRequestBytes( + bytes memory blockId, + uint8 numCallData, + bytes memory callData // Created with buildEthCallRecordBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked(uint32(blockId.length), blockId, numCallData, callData); + } - // According to the spec, there may be at most 16 seeds. - // https://github.com/gagliardetto/solana-go/blob/6fe3aea02e3660d620433444df033fc3fe6e64c1/keys.go#L559 - uint public constant SolanaMaxSeeds = 16; - - // According to the spec, a seed may be at most 32 bytes. - // https://github.com/gagliardetto/solana-go/blob/6fe3aea02e3660d620433444df033fc3fe6e64c1/keys.go#L557 - uint public constant SolanaMaxSeedLen = 32; - - /// @dev buildSolanaPdaSeedBytes packs the seeds for a PDA entry into an array of bytes. - function buildSolanaPdaSeedBytes( - bytes[] memory _seeds - ) internal pure returns (bytes memory, uint8){ - uint numSeeds = _seeds.length; - if (numSeeds > SolanaMaxSeeds) { - revert SolanaTooManySeeds(); - } - - bytes memory result; - - for (uint idx; idx < numSeeds;) { - uint seedLen = _seeds[idx].length; - if (seedLen > SolanaMaxSeedLen) { - revert SolanaSeedTooLong(); - } - result = abi.encodePacked( - result, - abi.encodePacked( - uint32(seedLen), - _seeds[idx] - ) - ); - - unchecked { ++idx; } - } - - return (result, uint8(numSeeds)); - } + function buildEthCallByTimestampRequestBytes( + uint64 targetTimeUs, + bytes memory targetBlockHint, + bytes memory followingBlockHint, + uint8 numCallData, + bytes memory callData // Created with buildEthCallRecordBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked( + targetTimeUs, + uint32(targetBlockHint.length), + targetBlockHint, + uint32(followingBlockHint.length), + followingBlockHint, + numCallData, + callData + ); + } - // - // Query Response stuff - // - - /// @dev buildQueryResponseBytes builds a query response from the specified fields. - function buildQueryResponseBytes( - uint8 _version, - uint16 _senderChainId, - bytes memory _signature, - bytes memory _queryRequest, - uint8 _numPerChainResponses, - bytes memory _perChainResponses - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _version, - _senderChainId, - _signature, - uint32(_queryRequest.length), - _queryRequest, - _numPerChainResponses, - _perChainResponses // Each created by buildPerChainResponseBytes() - ); - } + function buildEthCallWithFinalityRequestBytes( + bytes memory blockId, + bytes memory finality, + uint8 numCallData, + bytes memory callData // Created with buildEthCallRecordBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked( + uint32(blockId.length), + blockId, + uint32(finality.length), + finality, + numCallData, + callData + ); + } - /// @dev buildPerChainResponseBytes builds a per chain response from the specified fields. - function buildPerChainResponseBytes( - uint16 _chainId, - uint8 _queryType, - bytes memory _responseBytes - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _chainId, - _queryType, - uint32(_responseBytes.length), - _responseBytes - ); - } + function buildEthCallRecordBytes( + address contractAddress, + bytes memory callData + ) internal pure returns (bytes memory){ + return abi.encodePacked(contractAddress, uint32(callData.length), callData); + } - /// @dev buildEthCallResponseBytes builds an eth_call response from the specified fields. - function buildEthCallResponseBytes( - uint64 _blockNumber, - bytes32 _blockHash, - uint64 _blockTimeUs, - uint8 _numResults, - bytes memory _results // Created with buildEthCallResultBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _blockNumber, - _blockHash, - _blockTimeUs, - _numResults, - _results - ); - } + function buildSolanaAccountRequestBytes( + bytes memory commitment, + uint64 minContextSlot, + uint64 dataSliceOffset, + uint64 dataSliceLength, + uint8 numAccounts, + bytes memory accounts // Each account is 32 bytes. + ) internal pure returns (bytes memory){ + return abi.encodePacked( + uint32(commitment.length), + commitment, + minContextSlot, + dataSliceOffset, + dataSliceLength, + numAccounts, + accounts + ); + } + + function buildSolanaPdaRequestBytes( + bytes memory commitment, + uint64 minContextSlot, + uint64 dataSliceOffset, + uint64 dataSliceLength, + bytes[] memory pdas // Created with multiple calls to buildSolanaPdaEntry() + ) internal pure returns (bytes memory){ + uint numPdas = pdas.length; + if (numPdas > type(uint8).max) + revert SolanaTooManyPDAs(); + + bytes memory result = abi.encodePacked( + uint32(commitment.length), + commitment, + minContextSlot, + dataSliceOffset, + dataSliceLength, + uint8(numPdas) + ); + + for (uint idx; idx < numPdas;) { + result = abi.encodePacked(result, pdas[idx]); - /// @dev buildEthCallByTimestampResponseBytes builds an eth_call_by_timestamp response from the specified fields. - function buildEthCallByTimestampResponseBytes( - uint64 _targetBlockNumber, - bytes32 _targetBlockHash, - uint64 _targetBlockTimeUs, - uint64 _followingBlockNumber, - bytes32 _followingBlockHash, - uint64 _followingBlockTimeUs, - uint8 _numResults, - bytes memory _results // Created with buildEthCallResultBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _targetBlockNumber, - _targetBlockHash, - _targetBlockTimeUs, - _followingBlockNumber, - _followingBlockHash, - _followingBlockTimeUs, - _numResults, - _results - ); + unchecked { ++idx; } } - /// @dev buildEthCallWithFinalityResponseBytes builds an eth_call_with_finality response from the specified fields. Note that it is currently the same as buildEthCallResponseBytes. - function buildEthCallWithFinalityResponseBytes( - uint64 _blockNumber, - bytes32 _blockHash, - uint64 _blockTimeUs, - uint8 _numResults, - bytes memory _results // Created with buildEthCallResultBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _blockNumber, - _blockHash, - _blockTimeUs, - _numResults, - _results - ); - } - - /// @dev buildEthCallResultBytes builds an eth_call result from the specified fields. - function buildEthCallResultBytes( - bytes memory _result - ) internal pure returns (bytes memory){ - return abi.encodePacked( - uint32(_result.length), - _result - ); + return result; + } + + function buildSolanaPdaEntry( + bytes32 programId, + uint8 numSeeds, + bytes memory seeds // Created with buildSolanaPdaSeedBytes() + ) internal pure returns (bytes memory){ + if (numSeeds > SOLANA_MAX_SEEDS) + revert SolanaTooManySeeds(); + + return abi.encodePacked(programId, numSeeds, seeds); + } + + //packs the seeds for a PDA entry into an array of bytes. + function buildSolanaPdaSeedBytes( + bytes[] memory seeds + ) internal pure returns (bytes memory, uint8){ + uint numSeeds = seeds.length; + if (numSeeds > SOLANA_MAX_SEEDS) + revert SolanaTooManySeeds(); + + bytes memory result; + + for (uint idx; idx < numSeeds;) { + uint seedLen = seeds[idx].length; + if (seedLen > SOLANA_MAX_SEED_LEN) + revert SolanaSeedTooLong(); + + result = abi.encodePacked(result, abi.encodePacked(uint32(seedLen), seeds[idx])); + + unchecked { ++idx; } } - /// @dev buildSolanaAccountResponseBytes builds a sol_account response from the specified fields. - function buildSolanaAccountResponseBytes( - uint64 _slotNumber, - uint64 _blockTimeUs, - bytes32 _blockHash, - uint8 _numResults, - bytes memory _results // Created with buildEthCallResultBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _slotNumber, - _blockTimeUs, - _blockHash, - _numResults, - _results - ); - } - - /// @dev buildSolanaPdaResponseBytes builds a sol_pda response from the specified fields. - function buildSolanaPdaResponseBytes( - uint64 _slotNumber, - uint64 _blockTimeUs, - bytes32 _blockHash, - uint8 _numResults, - bytes memory _results // Created with buildEthCallResultBytes() - ) internal pure returns (bytes memory){ - return abi.encodePacked( - _slotNumber, - _blockTimeUs, - _blockHash, - _numResults, - _results - ); - } + return (result, uint8(numSeeds)); + } + + // --- Query Response --- + + function buildQueryResponseBytes( + uint8 version, + uint16 senderChainId, + bytes memory signature, + bytes memory queryRequest, + uint8 numPerChainResponses, + bytes memory perChainResponses + ) internal pure returns (bytes memory){ + return abi.encodePacked( + version, + senderChainId, + signature, + uint32(queryRequest.length), + queryRequest, + numPerChainResponses, + perChainResponses // Each created by buildPerChainResponseBytes() + ); + } + + function buildPerChainResponseBytes( + uint16 chainId, + uint8 queryType, + bytes memory responseBytes + ) internal pure returns (bytes memory){ + return abi.encodePacked(chainId, queryType, uint32(responseBytes.length), responseBytes); + } + + function buildEthCallResponseBytes( + uint64 blockNumber, + bytes32 blockHash, + uint64 blockTimeUs, + uint8 numResults, + bytes memory results // Created with buildEthCallResultBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked(blockNumber, blockHash, blockTimeUs, numResults, results); + } + + function buildEthCallByTimestampResponseBytes( + uint64 targetBlockNumber, + bytes32 targetBlockHash, + uint64 targetBlockTimeUs, + uint64 followingBlockNumber, + bytes32 followingBlockHash, + uint64 followingBlockTimeUs, + uint8 numResults, + bytes memory results // Created with buildEthCallResultBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked( + targetBlockNumber, + targetBlockHash, + targetBlockTimeUs, + followingBlockNumber, + followingBlockHash, + followingBlockTimeUs, + numResults, + results + ); + } + + //Note this it is currently the same as buildEthCallResponseBytes. + function buildEthCallWithFinalityResponseBytes( + uint64 blockNumber, + bytes32 blockHash, + uint64 blockTimeUs, + uint8 numResults, + bytes memory results // Created with buildEthCallResultBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked(blockNumber, blockHash, blockTimeUs, numResults, results); + } + + function buildEthCallResultBytes( + bytes memory result + ) internal pure returns (bytes memory){ + return abi.encodePacked(uint32(result.length), result); + } + + function buildSolanaAccountResponseBytes( + uint64 slotNumber, + uint64 blockTimeUs, + bytes32 blockHash, + uint8 numResults, + bytes memory results // Created with buildEthCallResultBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked(slotNumber, blockTimeUs, blockHash, numResults, results); + } + + function buildSolanaPdaResponseBytes( + uint64 slotNumber, + uint64 blockTimeUs, + bytes32 blockHash, + uint8 numResults, + bytes memory results // Created with buildEthCallResultBytes() + ) internal pure returns (bytes memory){ + return abi.encodePacked(slotNumber, blockTimeUs, blockHash, numResults, results); + } } diff --git a/test/QueryResponse.t.sol b/test/QueryResponse.t.sol index 6c92977..a79b8c3 100644 --- a/test/QueryResponse.t.sol +++ b/test/QueryResponse.t.sol @@ -9,813 +9,849 @@ import "forge-std/Test.sol"; import "../src/testing/helpers/QueryTest.sol"; import {WormholeMock} from "../src/testing/helpers/WormholeMock.sol"; - -// @dev A non-abstract QueryResponse contract -contract QueryResponseContract is QueryResponse { - constructor(address _wormhole) QueryResponse(_wormhole) {} +//wrap library to allow testing via vm.expectRevert +contract QueryResponseLibWrapper { + function calcPrefixedResponseHash(bytes memory response) external pure returns (bytes32) { + return QueryResponseLib.calcPrefixedResponseHash(response); + } + + function parseAndVerifyQueryResponse( + address wormhole, + bytes memory response, + IWormhole.Signature[] memory signatures + ) external view returns (QueryResponse memory ret) { + return QueryResponseLib.parseAndVerifyQueryResponse(wormhole, response, signatures); + } + + function verifyQueryResponse( + address wormhole, + bytes memory response, + IWormhole.Signature[] memory signatures + ) external view { + return QueryResponseLib.verifyQueryResponse(wormhole, response, signatures); + } + + function parseQueryResponse( + bytes memory response + ) external pure returns (QueryResponse memory ret) { + return QueryResponseLib.parseQueryResponse(response); + } + + function parseEthCallQueryResponse( + PerChainQueryResponse memory pcr + ) external pure returns (EthCallQueryResponse memory ret) { + return QueryResponseLib.parseEthCallQueryResponse(pcr); + } + + function parseEthCallByTimestampQueryResponse( + PerChainQueryResponse memory pcr + ) external pure returns (EthCallByTimestampQueryResponse memory ret) { + return QueryResponseLib.parseEthCallByTimestampQueryResponse(pcr); + } + + function parseEthCallWithFinalityQueryResponse( + PerChainQueryResponse memory pcr + ) external pure returns (EthCallWithFinalityQueryResponse memory ret) { + return QueryResponseLib.parseEthCallWithFinalityQueryResponse(pcr); + } + + function parseSolanaAccountQueryResponse( + PerChainQueryResponse memory pcr + ) external pure returns (SolanaAccountQueryResponse memory ret) { + return QueryResponseLib.parseSolanaAccountQueryResponse(pcr); + } + + function parseSolanaPdaQueryResponse( + PerChainQueryResponse memory pcr + ) external pure returns (SolanaPdaQueryResponse memory ret) { + return QueryResponseLib.parseSolanaPdaQueryResponse(pcr); + } + + function validateBlockTime( + uint64 blockTimeInMicroSeconds, + uint256 minBlockTimeInSeconds + ) external pure { + QueryResponseLib.validateBlockTime(blockTimeInMicroSeconds, minBlockTimeInSeconds); + } + + function validateBlockNum(uint64 blockNum, uint256 minBlockNum) external pure { + QueryResponseLib.validateBlockNum(blockNum, minBlockNum); + } + + function validateChainId( + uint16 chainId, + uint16[] memory validChainIds + ) external pure { + QueryResponseLib.validateChainId(chainId, validChainIds); + } + + function validateEthCallRecord( + EthCallRecord[] memory ecds, + address[] memory validContractAddresses, + bytes4[] memory validFunctionSignatures + ) external pure { + QueryResponseLib.validateEthCallRecord(ecds, validContractAddresses, validFunctionSignatures); + } + + function validateEthCallRecord( + EthCallRecord memory ecd, + address[] memory validContractAddresses, //empty array means accept all + bytes4[] memory validFunctionSignatures //empty array means accept all + ) external pure { + QueryResponseLib.validateEthCallRecord(ecd, validContractAddresses, validFunctionSignatures); + } } contract TestQueryResponse is Test { - // Some happy case defaults - uint8 version = 0x01; - uint16 senderChainId = 0x0000; - bytes signature = hex"ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f200"; - uint32 queryRequestLen = 0x00000053; - uint8 queryRequestVersion = 0x01; - uint32 queryRequestNonce = 0xdd9914c6; - uint8 numPerChainQueries = 0x01; - bytes perChainQueries = hex"0005010000004600000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; - bytes perChainQueriesInner = hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; - uint8 numPerChainResponses = 0x01; - bytes perChainResponses = hex"000501000000b90000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a"; - bytes perChainResponsesInner = hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; - - bytes solanaAccountSignature = hex"acb1d93cdfe60f9776e3e05d7fafaf9d83a1d14db70317230f6b0b6f3a60708a1a64dddac02d3843f4c516f2509b89454a2e73c360fea47beee1c1a091ff9f3201"; - uint32 solanaAccountQueryRequestLen = 0x00000073; - uint8 solanaAccountQueryRequestVersion = 0x01; - uint32 solanaAccountQueryRequestNonce = 0x0000002a; - uint8 solanaAccountNumPerChainQueries = 0x01; - bytes solanaAccountPerChainQueries = hex"000104000000660000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"; - bytes solanaAccountPerChainQueriesInner = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"; - uint8 solanaAccountNumPerChainResponses = 0x01; - bytes solanaAccountPerChainResponses = hex"010001040000013f000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; - bytes solanaAccountPerChainResponsesInner = hex"000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; - - bytes solanaPdaSignature = hex"0c8418d81c00aad6283ba3eb30e141ccdd9296e013ca44e5cc713418921253004b93107ba0d858a548ce989e2bca4132e4c2f9a57a9892e3a87a8304cdb36d8f00"; - uint32 solanaPdaQueryRequestLen = 0x0000006b; - uint8 solanaPdaQueryRequestVersion = 0x01; - uint32 solanaPdaQueryRequestNonce = 0x0000002b; - uint8 solanaPdaNumPerChainQueries = 0x01; - bytes solanaPdaPerChainQueries = hex"010001050000005e0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; - bytes solanaPdaPerChainQueriesInner = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; - uint8 solanaPdaNumPerChainResponses = 0x01; - bytes solanaPdaPerChainResponses = hex"0001050000009b00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"; - bytes solanaPdaPerChainResponsesInner = hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"; - - uint8 sigGuardianIndex = 0; - - QueryResponse queryResponse; - - function setUp() public { - WormholeMock wormholeMock = new WormholeMock(); - queryResponse = new QueryResponseContract(address(wormholeMock)); - } - - uint16 constant TEST_CHAIN_ID = 2; - address constant DEVNET_GUARDIAN = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe; - uint256 constant DEVNET_GUARDIAN_PRIVATE_KEY = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; - uint16 constant GOVERNANCE_CHAIN_ID = 1; - bytes32 constant GOVERNANCE_CONTRACT = 0x0000000000000000000000000000000000000000000000000000000000000004; - - function getSignature(bytes memory response) internal view returns (uint8 v, bytes32 r, bytes32 s) { - bytes32 responseDigest = queryResponse.getResponseDigest(response); - (v, r, s) = vm.sign(DEVNET_GUARDIAN_PRIVATE_KEY, responseDigest); - } - - function concatenateQueryResponseBytesOffChain( - uint8 _version, - uint16 _senderChainId, - bytes memory _signature, - uint8 _queryRequestVersion, - uint32 _queryRequestNonce, - uint8 _numPerChainQueries, - bytes memory _perChainQueries, - uint8 _numPerChainResponses, - bytes memory _perChainResponses - ) internal pure returns (bytes memory){ - bytes memory queryRequest = QueryTest.buildOffChainQueryRequestBytes( - _queryRequestVersion, - _queryRequestNonce, - _numPerChainQueries, - _perChainQueries - ); - return QueryTest.buildQueryResponseBytes( - _version, - _senderChainId, - _signature, - queryRequest, - _numPerChainResponses, - _perChainResponses - ); - } - - function test_getResponseHash() public { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - bytes32 hash = queryResponse.getResponseHash(resp); - bytes32 expectedHash = 0xed18e80906ffa80ce953a132a9cbbcf84186955f8fc8ce0322cd68622a58570e; - assertEq(hash, expectedHash); - } - - function test_getResponseDigest() public { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - bytes32 digest = queryResponse.getResponseDigest(resp); - bytes32 expectedDigest = 0x5b84b19c68ee0b37899230175a92ee6eda4c5192e8bffca1d057d811bb3660e2; - assertEq(digest, expectedDigest); - } - - function test_verifyQueryResponseSignatures() public view { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - queryResponse.verifyQueryResponseSignatures(resp, signatures); - // TODO: There are no assertions for this test - } - - function test_parseAndVerifyQueryResponse() public { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - ParsedQueryResponse memory r = queryResponse.parseAndVerifyQueryResponse(resp, signatures); - assertEq(r.version, 1); - assertEq(r.senderChainId, 0); - assertEq(r.requestId, hex"ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f200"); - assertEq(r.nonce, 3717797062); - assertEq(r.responses.length, 1); - assertEq(r.responses[0].chainId, 5); - assertEq(r.responses[0].queryType, 1); - assertEq(r.responses[0].request, hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"); - assertEq(r.responses[0].response, hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a"); - } - - function test_parseEthCallQueryResponse() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 5, - queryType: 1, - request: hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd", - response: hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a" - }); - - EthCallQueryResponse memory eqr = queryResponse.parseEthCallQueryResponse(r); - assertEq(eqr.requestBlockId, hex"307832613631616334"); - assertEq(eqr.blockNum, 44440260); - assertEq(eqr.blockHash, hex"c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d04"); - assertEq(eqr.blockTime, 1687961579000000); - assertEq(eqr.result.length, 2); - - assertEq(eqr.result[0].contractAddress, address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270)); - assertEq(eqr.result[0].callData, hex"06fdde03"); - assertEq(eqr.result[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000"); - - assertEq(eqr.result[1].contractAddress, address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270)); - assertEq(eqr.result[1].callData, hex"18160ddd"); - assertEq(eqr.result[1].result, hex"0000000000000000000000000000000000000000007ae5649beabeddf889364a"); - } - - function test_parseEthCallQueryResponseRevertWrongQueryType() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 5, - queryType: 2, - request: hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd", - response: hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a" - }); - - vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 2, queryResponse.QT_ETH_CALL())); - queryResponse.parseEthCallQueryResponse(r); - } - - function test_parseEthCallQueryResponseComparison() public { - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 23, - queryType: 1, - request: hex"00000009307832376433333433013ce792601c936b1c81f73ea2fa77208c0a478bae00000004916d5743", - response: hex"00000000027d3343b9848f128b3658a0b9b50aa174e3ddc15ac4e54c84ee534b6d247adbdfc300c90006056cda47a84001000000200000000000000000000000000000000000000000000000000000000000000004" - }); - - EthCallQueryResponse memory eqr = queryResponse.parseEthCallQueryResponse(r); - assertEq(eqr.requestBlockId, "0x27d3343"); - assertEq(eqr.blockNum, 0x27d3343); - assertEq(eqr.blockHash, hex"b9848f128b3658a0b9b50aa174e3ddc15ac4e54c84ee534b6d247adbdfc300c9"); - vm.warp(1694814937); - assertEq(eqr.blockTime / 1_000_000, block.timestamp); - assertEq(eqr.result.length, 1); - - assertEq(eqr.result[0].contractAddress, address(0x3ce792601c936b1c81f73Ea2fa77208C0A478BaE)); - assertEq(eqr.result[0].callData, hex"916d5743"); - bytes memory callData = eqr.result[0].callData; - bytes4 callSignature; - assembly { - callSignature := mload(add(callData, 32)) - } - assertEq(callSignature, bytes4(keccak256("getMyCounter()"))); - assertEq(eqr.result[0].result, hex"0000000000000000000000000000000000000000000000000000000000000004"); - assertEq(abi.decode(eqr.result[0].result, (uint256)), 4); - } - - function test_parseEthCallByTimestampQueryResponse() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 2, - request: hex"00000003f4810cc0000000063078343237310000000630783432373202ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", - response: hex"0000000000004271ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b0800000003f4810cc000000000000042720b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb00000003f4904f0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" - }); - - EthCallByTimestampQueryResponse memory eqr = queryResponse.parseEthCallByTimestampQueryResponse(r); - assertEq(eqr.requestTargetBlockIdHint, hex"307834323731"); - assertEq(eqr.requestFollowingBlockIdHint, hex"307834323732"); - assertEq(eqr.requestTargetTimestamp, 0x03f4810cc0); - assertEq(eqr.targetBlockNum, 0x0000000000004271); - assertEq(eqr.targetBlockHash, hex"ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b08"); - assertEq(eqr.targetBlockTime, 0x03f4810cc0); - assertEq(eqr.followingBlockNum, 0x0000000000004272); - assertEq(eqr.followingBlockHash, hex"0b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb"); - assertEq(eqr.followingBlockTime, 0x03f4904f00); - assertEq(eqr.result.length, 2); - - assertEq(eqr.result[0].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); - assertEq(eqr.result[0].callData, hex"06fdde03"); - assertEq(eqr.result[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); - - assertEq(eqr.result[1].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); - assertEq(eqr.result[1].callData, hex"18160ddd"); - assertEq(eqr.result[1].result, hex"0000000000000000000000000000000000000000000000000000000000000000"); - } - - function test_parseEthCallByTimestampQueryResponseRevertWrongQueryType() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 1, - request: hex"00000003f4810cc0000000063078343237310000000630783432373202ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", - response: hex"0000000000004271ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b0800000003f4810cc000000000000042720b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb00000003f4904f0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" - }); - - vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, queryResponse.QT_ETH_CALL_BY_TIMESTAMP())); - queryResponse.parseEthCallByTimestampQueryResponse(r); - } - - function test_parseEthCallWithFinalityQueryResponse() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 3, - request: hex"000000063078363032390000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", - response: hex"00000000000060299eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d700000005bb1bd58002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" - }); - - EthCallWithFinalityQueryResponse memory eqr = queryResponse.parseEthCallWithFinalityQueryResponse(r); - assertEq(eqr.requestBlockId, hex"307836303239"); - assertEq(eqr.requestFinality, hex"66696e616c697a6564"); - assertEq(eqr.blockNum, 0x6029); - assertEq(eqr.blockHash, hex"9eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d7"); - assertEq(eqr.blockTime, 0x05bb1bd580); - assertEq(eqr.result.length, 2); - - assertEq(eqr.result[0].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); - assertEq(eqr.result[0].callData, hex"06fdde03"); - assertEq(eqr.result[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); - - assertEq(eqr.result[1].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); - assertEq(eqr.result[1].callData, hex"18160ddd"); - assertEq(eqr.result[1].result, hex"0000000000000000000000000000000000000000000000000000000000000000"); - } - - function test_parseEthCallWithFinalityQueryResponseRevertWrongQueryType() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 1, - request: hex"000000063078363032390000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", - response: hex"00000000000060299eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d700000005bb1bd58002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" - }); - - vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, queryResponse.QT_ETH_CALL_WITH_FINALITY())); - queryResponse.parseEthCallWithFinalityQueryResponse(r); - } - - // Start of Solana Stuff /////////////////////////////////////////////////////////////////////////// - - function test_verifyQueryResponseSignaturesForSolana() public view { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, solanaAccountSignature, solanaAccountQueryRequestVersion, solanaAccountQueryRequestNonce, solanaAccountNumPerChainQueries, solanaAccountPerChainQueries, solanaAccountNumPerChainResponses, solanaAccountPerChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - queryResponse.verifyQueryResponseSignatures(resp, signatures); - // TODO: There are no assertions for this test - } - - function test_parseSolanaAccountQueryResponse() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 4, - request: solanaAccountPerChainQueriesInner, - response: solanaAccountPerChainResponsesInner - }); - - SolanaAccountQueryResponse memory sar = queryResponse.parseSolanaAccountQueryResponse(r); - - assertEq(sar.requestCommitment, "finalized"); - assertEq(sar.requestMinContextSlot, 0); - assertEq(sar.requestDataSliceOffset, 0); - assertEq(sar.requestDataSliceLength, 0); - assertEq(sar.slotNumber, 0xd85f); - assertEq(sar.blockTime, 0x00060f3e9915ddc0); - assertEq(sar.blockHash, hex"3a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b"); - assertEq(sar.results.length, 2); - - assertEq(sar.results[0].account, hex"165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa301"); - assertEq(sar.results[0].lamports, 0x164d60); - assertEq(sar.results[0].rentEpoch, 0); - assertEq(sar.results[0].executable, false); - assertEq(sar.results[0].owner, hex"06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"); - assertEq(sar.results[0].data, hex"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000"); - - assertEq(sar.results[1].account, hex"9c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"); - assertEq(sar.results[1].lamports, 0x164d60); - assertEq(sar.results[1].rentEpoch, 0); - assertEq(sar.results[1].executable, false); - assertEq(sar.results[1].owner, hex"06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"); - assertEq(sar.results[1].data, hex"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"); - } - - function test_parseSolanaAccountQueryResponseRevertWrongQueryType() public { - // Pass an ETH per chain response into the Solana parser. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 1, - request: solanaAccountPerChainQueriesInner, - response: solanaAccountPerChainResponsesInner - }); - - vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, queryResponse.QT_SOL_ACCOUNT())); - queryResponse.parseSolanaAccountQueryResponse(r); - } - - function test_parseSolanaAccountQueryResponseRevertUnexpectedNumberOfResults() public { - // Only one account on the request but two in the response. - bytes memory requestWithOnlyOneAccount = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000001165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa301"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 4, - request: requestWithOnlyOneAccount, - response: solanaAccountPerChainResponsesInner - }); - - vm.expectRevert(UnexpectedNumberOfResults.selector); - queryResponse.parseSolanaAccountQueryResponse(r); - } - - function test_parseSolanaAccountQueryResponseExtraRequestBytesRevertInvalidPayloadLength() public { - // Extra bytes at the end of the request. - bytes memory requestWithExtraBytes = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7DEADBEEF"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 4, - request: requestWithExtraBytes, - response: solanaAccountPerChainResponsesInner - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 106, 102)); - queryResponse.parseSolanaAccountQueryResponse(r); - } - - function test_parseSolanaAccountQueryResponseExtraResponseBytesRevertInvalidPayloadLength() public { - // Extra bytes at the end of the response. - bytes memory responseWithExtraBytes = hex"000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000DEADBEEF"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 4, - request: solanaAccountPerChainQueriesInner, - response: responseWithExtraBytes - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 323, 319)); - queryResponse.parseSolanaAccountQueryResponse(r); - } - - function test_parseSolanaPdaQueryResponse() public { - // Take the data extracted by the previous test and break it down even further. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 5, - request: solanaPdaPerChainQueriesInner, - response: solanaPdaPerChainResponsesInner - }); - - SolanaPdaQueryResponse memory sar = queryResponse.parseSolanaPdaQueryResponse(r); - - assertEq(sar.requestCommitment, "finalized"); - assertEq(sar.requestMinContextSlot, 2303); - assertEq(sar.requestDataSliceOffset, 12); - assertEq(sar.requestDataSliceLength, 20); - assertEq(sar.slotNumber, 2303); - assertEq(sar.blockTime, 0x0006115e3f6d7540); - assertEq(sar.blockHash, hex"e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b"); - assertEq(sar.results.length, 1); - - assertEq(sar.results[0].programId, hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"); - assertEq(sar.results[0].seeds.length, 2); - assertEq(sar.results[0].seeds[0], hex"477561726469616e536574"); - assertEq(sar.results[0].seeds[1], hex"00000000"); - - assertEq(sar.results[0].account, hex"4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e"); - assertEq(sar.results[0].bump, 253); - assertEq(sar.results[0].lamports, 0x116ac0); - assertEq(sar.results[0].rentEpoch, 0); - assertEq(sar.results[0].executable, false); - assertEq(sar.results[0].owner, hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"); - assertEq(sar.results[0].data, hex"57cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"); - } + // Some happy case defaults + uint8 version = 0x01; + uint16 senderChainId = 0x0000; + bytes signature = hex"ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f200"; + uint32 queryRequestLen = 0x00000053; + uint8 queryRequestVersion = 0x01; + uint32 queryRequestNonce = 0xdd9914c6; + uint8 numPerChainQueries = 0x01; + bytes perChainQueries = hex"0005010000004600000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; + bytes perChainQueriesInner = hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; + uint8 numPerChainResponses = 0x01; + bytes perChainResponses = hex"000501000000b90000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a"; + bytes perChainResponsesInner = hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"; + + bytes solanaAccountSignature = hex"acb1d93cdfe60f9776e3e05d7fafaf9d83a1d14db70317230f6b0b6f3a60708a1a64dddac02d3843f4c516f2509b89454a2e73c360fea47beee1c1a091ff9f3201"; + uint32 solanaAccountQueryRequestLen = 0x00000073; + uint8 solanaAccountQueryRequestVersion = 0x01; + uint32 solanaAccountQueryRequestNonce = 0x0000002a; + uint8 solanaAccountNumPerChainQueries = 0x01; + bytes solanaAccountPerChainQueries = hex"000104000000660000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"; + bytes solanaAccountPerChainQueriesInner = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"; + uint8 solanaAccountNumPerChainResponses = 0x01; + bytes solanaAccountPerChainResponses = hex"010001040000013f000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; + bytes solanaAccountPerChainResponsesInner = hex"000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"; + + bytes solanaPdaSignature = hex"0c8418d81c00aad6283ba3eb30e141ccdd9296e013ca44e5cc713418921253004b93107ba0d858a548ce989e2bca4132e4c2f9a57a9892e3a87a8304cdb36d8f00"; + uint32 solanaPdaQueryRequestLen = 0x0000006b; + uint8 solanaPdaQueryRequestVersion = 0x01; + uint32 solanaPdaQueryRequestNonce = 0x0000002b; + uint8 solanaPdaNumPerChainQueries = 0x01; + bytes solanaPdaPerChainQueries = hex"010001050000005e0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; + bytes solanaPdaPerChainQueriesInner = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; + uint8 solanaPdaNumPerChainResponses = 0x01; + bytes solanaPdaPerChainResponses = hex"0001050000009b00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"; + bytes solanaPdaPerChainResponsesInner = hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"; + + uint8 constant SIG_GUARDIAN_INDEX = 0; + + address wormhole; + QueryResponseLibWrapper wrapper; + + function setUp() public { + wormhole = address(new WormholeMock()); + wrapper = new QueryResponseLibWrapper(); + } + + uint16 constant TEST_CHAIN_ID = 2; + address constant DEVNET_GUARDIAN = 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe; + uint256 constant DEVNET_GUARDIAN_PRIVATE_KEY = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; + uint16 constant GOVERNANCE_CHAIN_ID = 1; + bytes32 constant GOVERNANCE_CONTRACT = 0x0000000000000000000000000000000000000000000000000000000000000004; + + function getSignature( + bytes memory response + ) internal pure returns (IWormhole.Signature[] memory signatures) { + bytes32 responseDigest = QueryResponseLib.calcPrefixedResponseHash(response); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(DEVNET_GUARDIAN_PRIVATE_KEY, responseDigest); + signatures = new IWormhole.Signature[](1); + signatures[0] = IWormhole.Signature(r, s, v, SIG_GUARDIAN_INDEX); + } + + function concatenateQueryResponseBytesOffChain( + uint8 _version, + uint16 _senderChainId, + bytes memory _signature, + uint8 _queryRequestVersion, + uint32 _queryRequestNonce, + uint8 _numPerChainQueries, + bytes memory _perChainQueries, + uint8 _numPerChainResponses, + bytes memory _perChainResponses + ) internal pure returns (bytes memory){ + bytes memory queryRequest = QueryTest.buildOffChainQueryRequestBytes( + _queryRequestVersion, + _queryRequestNonce, + _numPerChainQueries, + _perChainQueries + ); + return QueryTest.buildQueryResponseBytes( + _version, + _senderChainId, + _signature, + queryRequest, + _numPerChainResponses, + _perChainResponses + ); + } + + function test_calcPrefixedResponseHash() public { + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + bytes32 digest = wrapper.calcPrefixedResponseHash(resp); + bytes32 expectedDigest = 0x5b84b19c68ee0b37899230175a92ee6eda4c5192e8bffca1d057d811bb3660e2; + assertEq(digest, expectedDigest); + } + + function test_verifyQueryResponse() public view { + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + wrapper.verifyQueryResponse(wormhole, resp, getSignature(resp)); + // TODO: There are no assertions for this test + } + + function test_parseAndVerifyQueryResponse() public { + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + QueryResponse memory r = wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + assertEq(r.version, 1); + assertEq(r.senderChainId, 0); + assertEq(r.requestId, hex"ff0c222dc9e3655ec38e212e9792bf1860356d1277462b6bf747db865caca6fc08e6317b64ee3245264e371146b1d315d38c867fe1f69614368dc4430bb560f200"); + assertEq(r.nonce, 3717797062); + assertEq(r.responses.length, 1); + assertEq(r.responses[0].chainId, 5); + assertEq(r.responses[0].queryType, 1); + assertEq(r.responses[0].request, hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd"); + assertEq(r.responses[0].response, hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a"); + } + + function test_parseEthCallQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 5, + queryType: 1, + request: hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd", + response: hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a" + }); + + EthCallQueryResponse memory eqr = wrapper.parseEthCallQueryResponse(r); + assertEq(eqr.requestBlockId, hex"307832613631616334"); + assertEq(eqr.blockNum, 44440260); + assertEq(eqr.blockHash, hex"c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d04"); + assertEq(eqr.blockTime, 1687961579000000); + assertEq(eqr.results.length, 2); + + assertEq(eqr.results[0].contractAddress, address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270)); + assertEq(eqr.results[0].callData, hex"06fdde03"); + assertEq(eqr.results[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000"); + + assertEq(eqr.results[1].contractAddress, address(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270)); + assertEq(eqr.results[1].callData, hex"18160ddd"); + assertEq(eqr.results[1].result, hex"0000000000000000000000000000000000000000007ae5649beabeddf889364a"); + } + + function test_parseEthCallQueryResponseRevertWrongQueryType() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 5, + queryType: 2, + request: hex"00000009307832613631616334020d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000406fdde030d500b1d8e8ef31e21c99d1db9a6444d3adf12700000000418160ddd", + response: hex"0000000002a61ac4c1adff9f6e180309e7d0d94c063338ddc61c1c4474cd6957c960efe659534d040005ff312e4f90c002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d57726170706564204d6174696300000000000000000000000000000000000000000000200000000000000000000000000000000000000000007ae5649beabeddf889364a" + }); + + vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 2, QueryType.ETH_CALL)); + wrapper.parseEthCallQueryResponse(r); + } + + function test_parseEthCallQueryResponseComparison() public { + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 23, + queryType: 1, + request: hex"00000009307832376433333433013ce792601c936b1c81f73ea2fa77208c0a478bae00000004916d5743", + response: hex"00000000027d3343b9848f128b3658a0b9b50aa174e3ddc15ac4e54c84ee534b6d247adbdfc300c90006056cda47a84001000000200000000000000000000000000000000000000000000000000000000000000004" + }); + + EthCallQueryResponse memory eqr = wrapper.parseEthCallQueryResponse(r); + assertEq(eqr.requestBlockId, "0x27d3343"); + assertEq(eqr.blockNum, 0x27d3343); + assertEq(eqr.blockHash, hex"b9848f128b3658a0b9b50aa174e3ddc15ac4e54c84ee534b6d247adbdfc300c9"); + vm.warp(1694814937); + assertEq(eqr.blockTime / 1_000_000, block.timestamp); + assertEq(eqr.results.length, 1); + + assertEq(eqr.results[0].contractAddress, address(0x3ce792601c936b1c81f73Ea2fa77208C0A478BaE)); + assertEq(eqr.results[0].callData, hex"916d5743"); + bytes memory callData = eqr.results[0].callData; + bytes4 callSignature; + assembly { callSignature := mload(add(callData, 32)) } + assertEq(callSignature, bytes4(keccak256("getMyCounter()"))); + assertEq(eqr.results[0].result, hex"0000000000000000000000000000000000000000000000000000000000000004"); + assertEq(abi.decode(eqr.results[0].result, (uint256)), 4); + } + + function test_parseEthCallByTimestampQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 2, + request: hex"00000003f4810cc0000000063078343237310000000630783432373202ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", + response: hex"0000000000004271ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b0800000003f4810cc000000000000042720b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb00000003f4904f0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + }); + + EthCallByTimestampQueryResponse memory eqr = wrapper.parseEthCallByTimestampQueryResponse(r); + assertEq(eqr.requestTargetBlockIdHint, hex"307834323731"); + assertEq(eqr.requestFollowingBlockIdHint, hex"307834323732"); + assertEq(eqr.requestTargetTimestamp, 0x03f4810cc0); + assertEq(eqr.targetBlockNum, 0x0000000000004271); + assertEq(eqr.targetBlockHash, hex"ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b08"); + assertEq(eqr.targetBlockTime, 0x03f4810cc0); + assertEq(eqr.followingBlockNum, 0x0000000000004272); + assertEq(eqr.followingBlockHash, hex"0b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb"); + assertEq(eqr.followingBlockTime, 0x03f4904f00); + assertEq(eqr.results.length, 2); + + assertEq(eqr.results[0].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); + assertEq(eqr.results[0].callData, hex"06fdde03"); + assertEq(eqr.results[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); + + assertEq(eqr.results[1].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); + assertEq(eqr.results[1].callData, hex"18160ddd"); + assertEq(eqr.results[1].result, hex"0000000000000000000000000000000000000000000000000000000000000000"); + } + + function test_parseEthCallByTimestampQueryResponseRevertWrongQueryType() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 1, + request: hex"00000003f4810cc0000000063078343237310000000630783432373202ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", + response: hex"0000000000004271ec70d2f70cf1933770ae760050a75334ce650aa091665ee43a6ed488cd154b0800000003f4810cc000000000000042720b1608c2cddfd9d7fb4ec94f79ec1389e2410e611a2c2bbde94e9ad37519ebbb00000003f4904f0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + }); + + vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, QueryType.ETH_CALL_BY_TIMESTAMP)); + wrapper.parseEthCallByTimestampQueryResponse(r); + } + + function test_parseEthCallWithFinalityQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 3, + request: hex"000000063078363032390000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", + response: hex"00000000000060299eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d700000005bb1bd58002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + }); + + EthCallWithFinalityQueryResponse memory eqr = wrapper.parseEthCallWithFinalityQueryResponse(r); + assertEq(eqr.requestBlockId, hex"307836303239"); + assertEq(eqr.requestFinality, hex"66696e616c697a6564"); + assertEq(eqr.blockNum, 0x6029); + assertEq(eqr.blockHash, hex"9eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d7"); + assertEq(eqr.blockTime, 0x05bb1bd580); + assertEq(eqr.results.length, 2); + + assertEq(eqr.results[0].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); + assertEq(eqr.results[0].callData, hex"06fdde03"); + assertEq(eqr.results[0].result, hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); + + assertEq(eqr.results[1].contractAddress, address(0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E)); + assertEq(eqr.results[1].callData, hex"18160ddd"); + assertEq(eqr.results[1].result, hex"0000000000000000000000000000000000000000000000000000000000000000"); + } + + function test_parseEthCallWithFinalityQueryResponseRevertWrongQueryType() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 1, + request: hex"000000063078363032390000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000418160ddd", + response: hex"00000000000060299eb9c56ffdae81214867ed217f5ab37e295c196b4f04b23a795d3e4aea6ff3d700000005bb1bd58002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + }); + + vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, QueryType.ETH_CALL_WITH_FINALITY)); + wrapper.parseEthCallWithFinalityQueryResponse(r); + } + + // Start of Solana Stuff /////////////////////////////////////////////////////////////////////////// + + function test_verifyQueryResponseForSolana() public view { + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, solanaAccountSignature, solanaAccountQueryRequestVersion, solanaAccountQueryRequestNonce, solanaAccountNumPerChainQueries, solanaAccountPerChainQueries, solanaAccountNumPerChainResponses, solanaAccountPerChainResponses); + wrapper.verifyQueryResponse(wormhole, resp, getSignature(resp)); + // TODO: There are no assertions for this test + } + + function test_parseSolanaAccountQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 4, + request: solanaAccountPerChainQueriesInner, + response: solanaAccountPerChainResponsesInner + }); + + SolanaAccountQueryResponse memory sar = wrapper.parseSolanaAccountQueryResponse(r); + + assertEq(sar.requestCommitment, "finalized"); + assertEq(sar.requestMinContextSlot, 0); + assertEq(sar.requestDataSliceOffset, 0); + assertEq(sar.requestDataSliceLength, 0); + assertEq(sar.slotNumber, 0xd85f); + assertEq(sar.blockTime, 0x00060f3e9915ddc0); + assertEq(sar.blockHash, hex"3a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b"); + assertEq(sar.results.length, 2); + + assertEq(sar.results[0].account, hex"165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa301"); + assertEq(sar.results[0].lamports, 0x164d60); + assertEq(sar.results[0].rentEpoch, 0); + assertEq(sar.results[0].executable, false); + assertEq(sar.results[0].owner, hex"06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"); + assertEq(sar.results[0].data, hex"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a0901000000000000000000000000000000000000000000000000000000000000000000000000"); + + assertEq(sar.results[1].account, hex"9c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"); + assertEq(sar.results[1].lamports, 0x164d60); + assertEq(sar.results[1].rentEpoch, 0); + assertEq(sar.results[1].executable, false); + assertEq(sar.results[1].owner, hex"06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9"); + assertEq(sar.results[1].data, hex"01000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"); + } + + function test_parseSolanaAccountQueryResponseRevertWrongQueryType() public { + // Pass an ETH per chain response into the Solana parser. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 1, + request: solanaAccountPerChainQueriesInner, + response: solanaAccountPerChainResponsesInner + }); + + vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, QueryType.SOLANA_ACCOUNT)); + wrapper.parseSolanaAccountQueryResponse(r); + } + + function test_parseSolanaAccountQueryResponseRevertUnexpectedNumberOfResults() public { + // Only one account on the request but two in the response. + bytes memory requestWithOnlyOneAccount = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000001165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa301"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 4, + request: requestWithOnlyOneAccount, + response: solanaAccountPerChainResponsesInner + }); + + vm.expectRevert(UnexpectedNumberOfResults.selector); + wrapper.parseSolanaAccountQueryResponse(r); + } + + function test_parseSolanaAccountQueryResponseExtraRequestBytesRevertInvalidPayloadLength() public { + // Extra bytes at the end of the request. + bytes memory requestWithExtraBytes = hex"0000000966696e616c697a656400000000000000000000000000000000000000000000000002165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7DEADBEEF"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 4, + request: requestWithExtraBytes, + response: solanaAccountPerChainResponsesInner + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 106, 102)); + wrapper.parseSolanaAccountQueryResponse(r); + } + + function test_parseSolanaAccountQueryResponseExtraResponseBytesRevertInvalidPayloadLength() public { + // Extra bytes at the end of the response. + bytes memory responseWithExtraBytes = hex"000000000000d85f00060f3e9915ddc03a8de2b1de609020bb0a0dcee594a8c06801619cf9ea2a498b9d910f9a25772b020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000DEADBEEF"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 4, + request: solanaAccountPerChainQueriesInner, + response: responseWithExtraBytes + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 323, 319)); + wrapper.parseSolanaAccountQueryResponse(r); + } + + function test_parseSolanaPdaQueryResponse() public { + // Take the data extracted by the previous test and break it down even further. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 5, + request: solanaPdaPerChainQueriesInner, + response: solanaPdaPerChainResponsesInner + }); + + SolanaPdaQueryResponse memory sar = wrapper.parseSolanaPdaQueryResponse(r); + + assertEq(sar.requestCommitment, "finalized"); + assertEq(sar.requestMinContextSlot, 2303); + assertEq(sar.requestDataSliceOffset, 12); + assertEq(sar.requestDataSliceLength, 20); + assertEq(sar.slotNumber, 2303); + assertEq(sar.blockTime, 0x0006115e3f6d7540); + assertEq(sar.blockHash, hex"e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b"); + assertEq(sar.results.length, 1); + + assertEq(sar.results[0].programId, hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"); + assertEq(sar.results[0].seeds.length, 2); + assertEq(sar.results[0].seeds[0], hex"477561726469616e536574"); + assertEq(sar.results[0].seeds[1], hex"00000000"); + + assertEq(sar.results[0].account, hex"4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773e"); + assertEq(sar.results[0].bump, 253); + assertEq(sar.results[0].lamports, 0x116ac0); + assertEq(sar.results[0].rentEpoch, 0); + assertEq(sar.results[0].executable, false); + assertEq(sar.results[0].owner, hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"); + assertEq(sar.results[0].data, hex"57cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"); + } + + function test_parseSolanaPdaQueryResponseRevertWrongQueryType() public { + // Pass an ETH per chain response into the Solana parser. + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 2, + queryType: 1, + request: solanaPdaPerChainQueriesInner, + response: solanaPdaPerChainResponsesInner + }); + + vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, QueryType.SOLANA_PDA)); + wrapper.parseSolanaPdaQueryResponse(r); + } + + function test_parseSolanaPdaQueryResponseRevertUnexpectedNumberOfResults() public { + // Only one Pda on the request but two in the response. + bytes memory requestWithTwoPdas = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140202c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e536574000000040000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 5, + request: requestWithTwoPdas, + response: solanaPdaPerChainResponsesInner + }); + + vm.expectRevert(UnexpectedNumberOfResults.selector); + wrapper.parseSolanaPdaQueryResponse(r); + } + + function test_parseSolanaPdaQueryResponseExtraRequestBytesRevertInvalidPayloadLength() public { + // Extra bytes at the end of the request. + bytes memory requestWithExtraBytes = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000DEADBEEF"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 5, + request: requestWithExtraBytes, + response: solanaPdaPerChainResponsesInner + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 98, 94)); + wrapper.parseSolanaPdaQueryResponse(r); + } + + function test_parseSolanaPdaQueryResponseExtraResponseBytesRevertInvalidPayloadLength() public { + // Extra bytes at the end of the response. + bytes memory responseWithExtraBytes = hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65DEADBEEF"; + PerChainQueryResponse memory r = PerChainQueryResponse({ + chainId: 1, + queryType: 5, + request: solanaPdaPerChainQueriesInner, + response: responseWithExtraBytes + }); + + vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 159, 155)); + wrapper.parseSolanaPdaQueryResponse(r); + } + + /*********************************** + *********** FUZZ TESTS ************* + ***********************************/ + + function testFuzz_parseAndVerifyQueryResponse_fuzzVersion(uint8 _version) public { + vm.assume(_version != 1); + + bytes memory resp = concatenateQueryResponseBytesOffChain(_version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + vm.expectRevert(InvalidResponseVersion.selector); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzSenderChainId(uint16 _senderChainId) public { + vm.assume(_senderChainId != 0); + + bytes memory resp = concatenateQueryResponseBytesOffChain(version, _senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + // This could revert for multiple reasons. But the checkLength to ensure all the bytes are consumed is the backstop. + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzSignatureHappyCase(bytes memory _signature) public { + // This signature isn't validated in the QueryResponse library, therefore it could be an 65 byte hex string + vm.assume(_signature.length == 65); + + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, _signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + QueryResponse memory r = wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + + assertEq(r.requestId, _signature); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzSignatureUnhappyCase(bytes memory _signature) public { + // A signature that isn't 65 bytes long will always lead to a revert. The type of revert is unknown since it could be one of many. + vm.assume(_signature.length != 65); + + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, _signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestLen(uint32 _queryRequestLen, bytes calldata _perChainQueries) public { + // We add 6 to account for version + nonce + numPerChainQueries + vm.assume(_queryRequestLen != _perChainQueries.length + 6); + + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, _perChainQueries, numPerChainResponses, perChainResponses); + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestVersion(uint8 _version, uint8 _queryRequestVersion) public { + vm.assume(_version != _queryRequestVersion); + + bytes memory resp = concatenateQueryResponseBytesOffChain(_version, senderChainId, signature, _queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestNonce(uint32 _queryRequestNonce) public { + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, _queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + QueryResponse memory r = wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); - function test_parseSolanaPdaQueryResponseRevertWrongQueryType() public { - // Pass an ETH per chain response into the Solana parser. - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 2, - queryType: 1, - request: solanaPdaPerChainQueriesInner, - response: solanaPdaPerChainResponsesInner - }); - - vm.expectRevert(abi.encodeWithSelector(WrongQueryType.selector, 1, queryResponse.QT_SOL_PDA())); - queryResponse.parseSolanaPdaQueryResponse(r); - } - - function test_parseSolanaPdaQueryResponseRevertUnexpectedNumberOfResults() public { - // Only one Pda on the request but two in the response. - bytes memory requestWithTwoPdas = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140202c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e536574000000040000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 5, - request: requestWithTwoPdas, - response: solanaPdaPerChainResponsesInner - }); - - vm.expectRevert(UnexpectedNumberOfResults.selector); - queryResponse.parseSolanaPdaQueryResponse(r); - } - - function test_parseSolanaPdaQueryResponseExtraRequestBytesRevertInvalidPayloadLength() public { - // Extra bytes at the end of the request. - bytes memory requestWithExtraBytes = hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140102c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000DEADBEEF"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 5, - request: requestWithExtraBytes, - response: solanaPdaPerChainResponsesInner - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 98, 94)); - queryResponse.parseSolanaPdaQueryResponse(r); - } - - function test_parseSolanaPdaQueryResponseExtraResponseBytesRevertInvalidPayloadLength() public { - // Extra bytes at the end of the response. - bytes memory responseWithExtraBytes = hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65DEADBEEF"; - ParsedPerChainQueryResponse memory r = ParsedPerChainQueryResponse({ - chainId: 1, - queryType: 5, - request: solanaPdaPerChainQueriesInner, - response: responseWithExtraBytes - }); - - vm.expectRevert(abi.encodeWithSelector(InvalidPayloadLength.selector, 159, 155)); - queryResponse.parseSolanaPdaQueryResponse(r); - } - - /*********************************** - *********** FUZZ TESTS ************* - ***********************************/ - + assertEq(r.nonce, _queryRequestNonce); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzNumPerChainQueriesAndResponses(uint8 _numPerChainQueries, uint8 _numPerChainResponses) public { + vm.assume(_numPerChainQueries != _numPerChainResponses); + + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, _numPerChainQueries, perChainQueries, _numPerChainResponses, perChainResponses); + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzChainIds(uint16 _requestChainId, uint16 _responseChainId, uint256 _requestQueryType) public { + vm.assume(_requestChainId != _responseChainId); + _requestQueryType = bound(_requestQueryType, QueryType.min(), QueryType.max()); + + bytes memory packedPerChainQueries = abi.encodePacked(_requestChainId, uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); + bytes memory packedPerChainResponses = abi.encodePacked(_responseChainId, uint8(_requestQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); + vm.expectRevert(ChainIdMismatch.selector); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzMistmatchedRequestType(uint256 _requestQueryType, uint256 _responseQueryType) public { + _requestQueryType = bound(_requestQueryType, QueryType.min(), QueryType.max()); + _responseQueryType = bound(_responseQueryType, QueryType.min(), QueryType.max()); + vm.assume(_requestQueryType != _responseQueryType); + + bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); + bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(_responseQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); + vm.expectRevert(RequestTypeMismatch.selector); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzUnsupportedRequestType(uint8 _requestQueryType) public { + vm.assume(!QueryType.isValid(_requestQueryType)); + + bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); + bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); + vm.expectRevert(abi.encodeWithSelector(UnsupportedQueryType.selector, _requestQueryType)); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_parseAndVerifyQueryResponse_fuzzQueryBytesLength(uint32 _queryLength) public { + vm.assume(_queryLength != uint32(perChainQueriesInner.length)); + + bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(0x01), _queryLength, perChainQueriesInner); + bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(0x01), uint32(perChainResponsesInner.length), perChainResponsesInner); + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); + vm.expectRevert(); + wrapper.parseAndVerifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_verifyQueryResponse_validSignature(bytes calldata resp) public view { + // This should pass with a valid signature of any payload + wrapper.verifyQueryResponse(wormhole, resp, getSignature(resp)); + } + + function testFuzz_verifyQueryResponse_invalidSignature(bytes calldata resp, uint256 privateKey) public { + vm.assume(privateKey != DEVNET_GUARDIAN_PRIVATE_KEY); + // Less than secp256k1 curve + vm.assume(privateKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337); + vm.assume(privateKey != 0); + + (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(privateKey, wrapper.calcPrefixedResponseHash(resp)); + IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); + signatures[0] = IWormhole.Signature(sigR, sigS, sigV, SIG_GUARDIAN_INDEX); + vm.expectRevert(VerificationFailed.selector); + wrapper.verifyQueryResponse(wormhole, resp, signatures); + } + + function testFuzz_verifyQueryResponse_validSignatureWrongPrefix(bytes calldata responsePrefix) public { + vm.assume(keccak256(responsePrefix) != keccak256(QueryResponseLib.RESPONSE_PREFIX)); + bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); + bytes32 responseDigest = keccak256(abi.encodePacked(responsePrefix, keccak256(resp))); + + (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(DEVNET_GUARDIAN_PRIVATE_KEY, responseDigest); + IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); + signatures[0] = IWormhole.Signature(sigR, sigS, sigV, SIG_GUARDIAN_INDEX); + vm.expectRevert(VerificationFailed.selector); + wrapper.verifyQueryResponse(wormhole, resp, signatures); + } - function testFuzz_parseAndVerifyQueryResponse_fuzzVersion(uint8 _version) public { - vm.assume(_version != 1); - - bytes memory resp = concatenateQueryResponseBytesOffChain(_version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(InvalidResponseVersion.selector); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzSenderChainId(uint16 _senderChainId) public { - vm.assume(_senderChainId != 0); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, _senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - // This could revert for multiple reasons. But the checkLength to ensure all the bytes are consumed is the backstop. - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzSignatureHappyCase(bytes memory _signature) public { - // This signature isn't validated in the QueryResponse library, therefore it could be an 65 byte hex string - vm.assume(_signature.length == 65); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, _signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - ParsedQueryResponse memory r = queryResponse.parseAndVerifyQueryResponse(resp, signatures); - - assertEq(r.requestId, _signature); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzSignatureUnhappyCase(bytes memory _signature) public { - // A signature that isn't 65 bytes long will always lead to a revert. The type of revert is unknown since it could be one of many. - vm.assume(_signature.length != 65); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, _signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestLen(uint32 _queryRequestLen, bytes calldata _perChainQueries) public { - // We add 6 to account for version + nonce + numPerChainQueries - vm.assume(_queryRequestLen != _perChainQueries.length + 6); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, _perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestVersion(uint8 _version, uint8 _queryRequestVersion) public { - vm.assume(_version != _queryRequestVersion); - - bytes memory resp = concatenateQueryResponseBytesOffChain(_version, senderChainId, signature, _queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzQueryRequestNonce(uint32 _queryRequestNonce) public { - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, _queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - ParsedQueryResponse memory r = queryResponse.parseAndVerifyQueryResponse(resp, signatures); - - assertEq(r.nonce, _queryRequestNonce); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzNumPerChainQueriesAndResponses(uint8 _numPerChainQueries, uint8 _numPerChainResponses) public { - vm.assume(_numPerChainQueries != _numPerChainResponses); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, _numPerChainQueries, perChainQueries, _numPerChainResponses, perChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzChainIds(uint16 _requestChainId, uint16 _responseChainId, uint256 _requestQueryType) public { - vm.assume(_requestChainId != _responseChainId); - _requestQueryType = bound({x: _requestQueryType, min: queryResponse.QT_ETH_CALL(), max: queryResponse.QT_MAX() - 1}); - - bytes memory packedPerChainQueries = abi.encodePacked(_requestChainId, uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); - bytes memory packedPerChainResponses = abi.encodePacked(_responseChainId, uint8(_requestQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(ChainIdMismatch.selector); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzMistmatchedRequestType(uint256 _requestQueryType, uint256 _responseQueryType) public { - _requestQueryType = bound({x: _requestQueryType, min: queryResponse.QT_ETH_CALL(), max: queryResponse.QT_MAX() - 1}); - _responseQueryType = bound({x: _responseQueryType, min: queryResponse.QT_ETH_CALL(), max: queryResponse.QT_MAX() - 1}); - vm.assume(_requestQueryType != _responseQueryType); - - bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); - bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(_responseQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(RequestTypeMismatch.selector); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzUnsupportedRequestType(uint8 _requestQueryType) public { - vm.assume(_requestQueryType < queryResponse.QT_ETH_CALL() || _requestQueryType >= queryResponse.QT_MAX()); - - bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainQueriesInner.length), perChainQueriesInner); - bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(_requestQueryType), uint32(perChainResponsesInner.length), perChainResponsesInner); - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(abi.encodeWithSelector(UnsupportedQueryType.selector, _requestQueryType)); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_parseAndVerifyQueryResponse_fuzzQueryBytesLength(uint32 _queryLength) public { - vm.assume(_queryLength != uint32(perChainQueriesInner.length)); - - bytes memory packedPerChainQueries = abi.encodePacked(uint16(0x0005), uint8(0x01), _queryLength, perChainQueriesInner); - bytes memory packedPerChainResponses = abi.encodePacked(uint16(0x0005), uint8(0x01), uint32(perChainResponsesInner.length), perChainResponsesInner); - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, packedPerChainQueries, numPerChainResponses, packedPerChainResponses); - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert(); - queryResponse.parseAndVerifyQueryResponse(resp, signatures); - } - - function testFuzz_verifyQueryResponseSignatures_validSignature(bytes calldata resp) public view { - // This should pass with a valid signature of any payload - (uint8 sigV, bytes32 sigR, bytes32 sigS) = getSignature(resp); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - queryResponse.verifyQueryResponseSignatures(resp, signatures); - } - - function testFuzz_verifyQueryResponseSignatures_invalidSignature(bytes calldata resp, uint256 privateKey) public { - vm.assume(privateKey != DEVNET_GUARDIAN_PRIVATE_KEY); - // Less than secp256k1 curve - vm.assume(privateKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337); - vm.assume(privateKey != 0); - - (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(privateKey, queryResponse.getResponseDigest(resp)); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert("VM signature invalid"); - queryResponse.verifyQueryResponseSignatures(resp, signatures); - } - - function testFuzz_verifyQueryResponseSignatures_validSignatureWrongPrefix(bytes calldata responsePrefix) public { - vm.assume(keccak256(responsePrefix) != keccak256(queryResponse.responsePrefix())); - - bytes memory resp = concatenateQueryResponseBytesOffChain(version, senderChainId, signature, queryRequestVersion, queryRequestNonce, numPerChainQueries, perChainQueries, numPerChainResponses, perChainResponses); - bytes32 responseDigest = keccak256(abi.encodePacked(responsePrefix, keccak256(resp))); - - (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(DEVNET_GUARDIAN_PRIVATE_KEY, responseDigest); - IWormhole.Signature[] memory signatures = new IWormhole.Signature[](1); - signatures[0] = IWormhole.Signature({r: sigR, s: sigS, v: sigV, guardianIndex: sigGuardianIndex}); - vm.expectRevert("VM signature invalid"); - queryResponse.verifyQueryResponseSignatures(resp, signatures); - } - - uint64 constant private SECOND_RESOLUTION = 1_000_000; - uint64 constant private MAX_SECONDS = type(uint64).max/SECOND_RESOLUTION; - - function testFuzz_validateBlockTime_success(uint64 _blockTime, uint64 _minBlockTime) public view { - //assure: blockTime >= minBlockTime - _minBlockTime = uint64(bound(_minBlockTime, 0, MAX_SECONDS)); - _blockTime = uint64(bound(_blockTime, _minBlockTime, MAX_SECONDS)); - - queryResponse.validateBlockTime(_blockTime * SECOND_RESOLUTION, _minBlockTime); - } - - function testFuzz_validateBlockTime_fail(uint64 _blockTime, uint256 _minBlockTime) public { - //assure: blockTime < minBlockTime - vm.assume(_minBlockTime > 0); - uint upperBound = _minBlockTime <= MAX_SECONDS ? _minBlockTime-1 : MAX_SECONDS; - _blockTime = uint64(bound(_blockTime, 0, upperBound)); - - vm.expectRevert(StaleBlockTime.selector); - queryResponse.validateBlockTime(_blockTime * SECOND_RESOLUTION, _minBlockTime); - } - - function testFuzz_validateBlockNum_success(uint64 _blockNum, uint64 _minBlockNum) public view { - //assure: blockNum >= minBlockNum - _blockNum = uint64(bound(_blockNum, _minBlockNum, type(uint64).max)); - - queryResponse.validateBlockNum(_blockNum, _minBlockNum); - } - - function testFuzz_validateBlockNum_fail(uint256 _blockNum, uint256 _minBlockNum) public { - //assure: blockNum < minBlockNum - vm.assume(_minBlockNum > 0); - _blockNum = uint64(bound(_blockNum, 0, _minBlockNum-1)); - - vm.expectRevert(StaleBlockNum.selector); - queryResponse.validateBlockNum(uint64(_blockNum), _minBlockNum); - } - - function testFuzz_validateChainId_success(uint256 _validChainIndex, uint16[] memory _validChainIds) public view { - vm.assume(_validChainIds.length > 0); - _validChainIndex %= _validChainIds.length; - - queryResponse.validateChainId(_validChainIds[_validChainIndex], _validChainIds); - } - - function testFuzz_validateChainId_fail(uint16 _chainId, uint16[] memory _validChainIds) public { - for (uint16 i = 0; i < _validChainIds.length; ++i) { - vm.assume(_chainId != _validChainIds[i]); - } - - vm.expectRevert(InvalidChainId.selector); - queryResponse.validateChainId(_chainId, _validChainIds); - } - - function testFuzz_validateEthCallData_success(bytes memory randomBytes, uint256 _contractAddressIndex, uint256 _functionSignatureIndex, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public view { - vm.assume(_expectedContractAddresses.length > 0); - _contractAddressIndex %= _expectedContractAddresses.length; - vm.assume(_expectedFunctionSignatures.length > 0); - _functionSignatureIndex %= _expectedFunctionSignatures.length; - - EthCallData memory callData = EthCallData({ - contractAddress: _expectedContractAddresses[_contractAddressIndex], - callData: bytes.concat(_expectedFunctionSignatures[_functionSignatureIndex], randomBytes), - result: randomBytes - }); - - queryResponse.validateEthCallData(callData, _expectedContractAddresses, _expectedFunctionSignatures); - } - - function testFuzz_validateEthCallData_successZeroSignatures(bytes4 randomSignature, bytes memory randomBytes, uint256 _contractAddressIndex, address[] memory _expectedContractAddresses) public view { - vm.assume(_expectedContractAddresses.length > 0); - _contractAddressIndex %= _expectedContractAddresses.length; - - EthCallData memory callData = EthCallData({ - contractAddress: _expectedContractAddresses[_contractAddressIndex], - callData: bytes.concat(randomSignature, randomBytes), - result: randomBytes - }); + uint64 constant private MICROSECONDS_PER_SECOND = QueryResponseLib.MICROSECONDS_PER_SECOND; + uint64 constant private MAX_SECONDS = type(uint64).max/MICROSECONDS_PER_SECOND; + + function testFuzz_validateBlockTime_success(uint64 _blockTime, uint64 _minBlockTime) public view { + //assure: blockTime >= minBlockTime + _minBlockTime = uint64(bound(_minBlockTime, 0, MAX_SECONDS)); + _blockTime = uint64(bound(_blockTime, _minBlockTime, MAX_SECONDS)); + + wrapper.validateBlockTime(_blockTime * MICROSECONDS_PER_SECOND, _minBlockTime); + } - bytes4[] memory validSignatures = new bytes4[](0); + function testFuzz_validateBlockTime_fail(uint64 _blockTime, uint256 _minBlockTime) public { + //assure: blockTime < minBlockTime + vm.assume(_minBlockTime > 0); + uint upperBound = _minBlockTime <= MAX_SECONDS ? _minBlockTime-1 : MAX_SECONDS; + _blockTime = uint64(bound(_blockTime, 0, upperBound)); + + vm.expectRevert(StaleBlockTime.selector); + wrapper.validateBlockTime(_blockTime * MICROSECONDS_PER_SECOND, _minBlockTime); + } + + function testFuzz_validateBlockNum_success(uint64 _blockNum, uint64 _minBlockNum) public view { + //assure: blockNum >= minBlockNum + _blockNum = uint64(bound(_blockNum, _minBlockNum, type(uint64).max)); + + wrapper.validateBlockNum(_blockNum, _minBlockNum); + } - queryResponse.validateEthCallData(callData, _expectedContractAddresses, validSignatures); + function testFuzz_validateBlockNum_fail(uint256 _blockNum, uint256 _minBlockNum) public { + //assure: blockNum < minBlockNum + vm.assume(_minBlockNum > 0); + _blockNum = uint64(bound(_blockNum, 0, _minBlockNum-1)); + + vm.expectRevert(StaleBlockNum.selector); + wrapper.validateBlockNum(uint64(_blockNum), _minBlockNum); + } + + function testFuzz_validateChainId_success(uint256 _validChainIndex, uint16[] memory _validChainIds) public view { + vm.assume(_validChainIds.length > 0); + _validChainIndex %= _validChainIds.length; + + wrapper.validateChainId(_validChainIds[_validChainIndex], _validChainIds); + } + + function testFuzz_validateChainId_fail(uint16 _chainId, uint16[] memory _validChainIds) public { + for (uint16 i = 0; i < _validChainIds.length; ++i) { + vm.assume(_chainId != _validChainIds[i]); } - function testFuzz_validateEthCallData_successZeroAddresses(address randomAddress, bytes memory randomBytes, uint256 _functionSignatureIndex, bytes4[] memory _expectedFunctionSignatures) public view { - vm.assume(_expectedFunctionSignatures.length > 0); - _functionSignatureIndex %= _expectedFunctionSignatures.length; + vm.expectRevert(InvalidChainId.selector); + wrapper.validateChainId(_chainId, _validChainIds); + } + + function testFuzz_validateEthCallRecord_success(bytes memory randomBytes, uint256 _contractAddressIndex, uint256 _functionSignatureIndex, address[] memory _validContractAddresses, bytes4[] memory _validFunctionSignatures) public view { + vm.assume(randomBytes.length >= 4); + vm.assume(_validContractAddresses.length > 0); + _contractAddressIndex %= _validContractAddresses.length; + vm.assume(_validFunctionSignatures.length > 0); + _functionSignatureIndex %= _validFunctionSignatures.length; + + EthCallRecord memory callData = EthCallRecord({ + contractAddress: _validContractAddresses[_contractAddressIndex], + callData: bytes.concat(_validFunctionSignatures[_functionSignatureIndex], randomBytes), + result: randomBytes + }); + + wrapper.validateEthCallRecord(callData, _validContractAddresses, _validFunctionSignatures); + } + + function testFuzz_validateEthCallRecord_successZeroSignatures(bytes4 randomSignature, bytes memory randomBytes, uint256 _contractAddressIndex, address[] memory _validContractAddresses) public view { + vm.assume(_validContractAddresses.length > 0); + _contractAddressIndex %= _validContractAddresses.length; + + EthCallRecord memory callData = EthCallRecord({ + contractAddress: _validContractAddresses[_contractAddressIndex], + callData: bytes.concat(randomSignature, randomBytes), + result: randomBytes + }); + + bytes4[] memory validSignatures = new bytes4[](0); + + wrapper.validateEthCallRecord(callData, _validContractAddresses, validSignatures); + } + + function testFuzz_validateEthCallRecord_successZeroAddresses(address randomAddress, bytes memory randomBytes, uint256 _functionSignatureIndex, bytes4[] memory _validFunctionSignatures) public view { + vm.assume(randomBytes.length >= 4); + vm.assume(_validFunctionSignatures.length > 0); + _functionSignatureIndex %= _validFunctionSignatures.length; + + EthCallRecord memory callData = EthCallRecord({ + contractAddress: randomAddress, + callData: bytes.concat(_validFunctionSignatures[_functionSignatureIndex], randomBytes), + result: randomBytes + }); + + address[] memory validAddresses = new address[](0); - EthCallData memory callData = EthCallData({ - contractAddress: randomAddress, - callData: bytes.concat(_expectedFunctionSignatures[_functionSignatureIndex], randomBytes), - result: randomBytes - }); + wrapper.validateEthCallRecord(callData, validAddresses, _validFunctionSignatures); + } - address[] memory validAddresses = new address[](0); + function testFuzz_validateEthCallRecord_failSignature(bytes memory randomBytes, uint256 _contractAddressIndex, address[] memory _validContractAddresses, bytes4[] memory _validFunctionSignatures) public { + vm.assume(randomBytes.length >= 4); + vm.assume(_validContractAddresses.length > 0); + _contractAddressIndex %= _validContractAddresses.length; + vm.assume(_validFunctionSignatures.length > 0); - queryResponse.validateEthCallData(callData, validAddresses, _expectedFunctionSignatures); + for (uint256 i = 0; i < _validFunctionSignatures.length; ++i) { + vm.assume(bytes4(randomBytes) != _validFunctionSignatures[i]); } - function testFuzz_validateEthCallData_failSignature(bytes memory randomBytes, uint256 _contractAddressIndex, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public { - vm.assume(_expectedContractAddresses.length > 0); - _contractAddressIndex %= _expectedContractAddresses.length; - vm.assume(_expectedFunctionSignatures.length > 0); + EthCallRecord memory callData = EthCallRecord({ + contractAddress: _validContractAddresses[_contractAddressIndex], + callData: randomBytes, + result: randomBytes + }); - for (uint256 i = 0; i < _expectedFunctionSignatures.length; ++i) { - vm.assume(bytes4(randomBytes) != _expectedFunctionSignatures[i]); - } + vm.expectRevert(InvalidFunctionSignature.selector); + wrapper.validateEthCallRecord(callData, _validContractAddresses, _validFunctionSignatures); + } - EthCallData memory callData = EthCallData({ - contractAddress: _expectedContractAddresses[_contractAddressIndex], - callData: randomBytes, - result: randomBytes - }); + function testFuzz_validateEthCallRecord_failAddress(bytes memory randomBytes, address randomAddress, uint256 _functionSignatureIndex, address[] memory _validContractAddresses, bytes4[] memory _validFunctionSignatures) public { + vm.assume(_validFunctionSignatures.length > 0); + _functionSignatureIndex %= _validFunctionSignatures.length; + vm.assume(_validContractAddresses.length > 0); - vm.expectRevert(InvalidFunctionSignature.selector); - queryResponse.validateEthCallData(callData, _expectedContractAddresses, _expectedFunctionSignatures); + for (uint256 i = 0; i < _validContractAddresses.length; ++i) { + vm.assume(randomAddress != _validContractAddresses[i]); } - function testFuzz_validateEthCallData_failAddress(bytes memory randomBytes, address randomAddress, uint256 _functionSignatureIndex, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public { - vm.assume(_expectedFunctionSignatures.length > 0); - _functionSignatureIndex %= _expectedFunctionSignatures.length; - vm.assume(_expectedContractAddresses.length > 0); + EthCallRecord memory callData = EthCallRecord({ + contractAddress: randomAddress, + callData: bytes.concat(_validFunctionSignatures[_functionSignatureIndex], randomBytes), + result: randomBytes + }); - for (uint256 i = 0; i < _expectedContractAddresses.length; ++i) { - vm.assume(randomAddress != _expectedContractAddresses[i]); - } + vm.expectRevert(InvalidContractAddress.selector); + wrapper.validateEthCallRecord(callData, _validContractAddresses, _validFunctionSignatures); + } - EthCallData memory callData = EthCallData({ - contractAddress: randomAddress, - callData: bytes.concat(_expectedFunctionSignatures[_functionSignatureIndex], randomBytes), - result: randomBytes - }); + function testFuzz_validateMultipleEthCallRecord_success(uint8 numInputs, bytes memory randomBytes, uint256 _contractAddressIndex, uint256 _functionSignatureIndex, address[] memory _validContractAddresses, bytes4[] memory _validFunctionSignatures) public view { + vm.assume(_validContractAddresses.length > 0); + _contractAddressIndex %= _validContractAddresses.length; + vm.assume(_validFunctionSignatures.length > 0); + _functionSignatureIndex %= _validFunctionSignatures.length; - vm.expectRevert(InvalidContractAddress.selector); - queryResponse.validateEthCallData(callData, _expectedContractAddresses, _expectedFunctionSignatures); - } + EthCallRecord[] memory callDatas = new EthCallRecord[](numInputs); - function testFuzz_validateMultipleEthCallData_success(uint8 numInputs, bytes memory randomBytes, uint256 _contractAddressIndex, uint256 _functionSignatureIndex, address[] memory _expectedContractAddresses, bytes4[] memory _expectedFunctionSignatures) public view { - vm.assume(_expectedContractAddresses.length > 0); - _contractAddressIndex %= _expectedContractAddresses.length; - vm.assume(_expectedFunctionSignatures.length > 0); - _functionSignatureIndex %= _expectedFunctionSignatures.length; + for (uint256 i = 0; i < numInputs; ++i) { + callDatas[i] = EthCallRecord({ + contractAddress: _validContractAddresses[_contractAddressIndex], + callData: bytes.concat(_validFunctionSignatures[_functionSignatureIndex], randomBytes), + result: randomBytes + }); + } - EthCallData[] memory callDatas = new EthCallData[](numInputs); - - for (uint256 i = 0; i < numInputs; ++i) { - callDatas[i] = EthCallData({ - contractAddress: _expectedContractAddresses[_contractAddressIndex], - callData: bytes.concat(_expectedFunctionSignatures[_functionSignatureIndex], randomBytes), - result: randomBytes - }); - } - - queryResponse.validateMultipleEthCallData(callDatas, _expectedContractAddresses, _expectedFunctionSignatures); - } + wrapper.validateEthCallRecord(callDatas, _validContractAddresses, _validFunctionSignatures); + } } diff --git a/test/QueryTest.t.sol b/test/QueryTest.t.sol index 1d63ac6..91d6965 100644 --- a/test/QueryTest.t.sol +++ b/test/QueryTest.t.sol @@ -1,292 +1,284 @@ // SPDX-License-Identifier: Apache 2 -// forge test --match-contract QueryTest - pragma solidity ^0.8.4; import "forge-std/Test.sol"; -import "../src/testing/helpers/QueryTest.sol"; - -contract TestQueryTest is Test { - // - // Query Request tests - // - - function test_buildOffChainQueryRequestBytes() public { - bytes memory req = QueryTest.buildOffChainQueryRequestBytes( - /* version */ 1, - /* nonce */ 1, - /* numPerChainQueries */ 1, - /* perChainQueries */ hex"0002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" - ); - assertEq(req, hex"0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); - } - - function test_buildPerChainRequestBytes() public { - bytes memory pcr = QueryTest.buildPerChainRequestBytes( - /* chainId */ 2, - /* queryType */ 1, - /* queryBytes */ hex"00000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" - ); - assertEq(pcr, hex"0002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); - } - - function test_buildEthCallRequestBytes() public { - bytes memory ecr = QueryTest.buildEthCallRequestBytes( - /* blockId */ "0x744", - /* numCallData */ 2, - /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" - ); - assertEq(ecr, hex"00000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); - } - - function test_buildEthCallByTimestampRequestBytes() public { - bytes memory ecr = QueryTest.buildEthCallByTimestampRequestBytes( - /* targetTimeUs */ 0x10642ac0, - /* targetBlockHint */ "0x15d", - /* followingBlockHint */ "0x15e", - /* numCallData */ 2, - /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" - ); - assertEq(ecr, hex"0000000010642ac000000005307831356400000005307831356502ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); - } +import "wormhole-sdk/testing/helpers/QueryTest.sol"; + +contract QueryRequestTest is Test { + function test_buildOffChainQueryRequestBytes() public { + bytes memory req = QueryTest.buildOffChainQueryRequestBytes( + /* version */ 1, + /* nonce */ 1, + /* numPerChainQueries */ 1, + /* perChainQueries */ hex"0002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" + ); + assertEq(req, hex"0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } + + function test_buildPerChainRequestBytes() public { + bytes memory pcr = QueryTest.buildPerChainRequestBytes( + /* chainId */ 2, + /* queryType */ 1, + /* queryBytes */ hex"00000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" + ); + assertEq(pcr, hex"0002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } + + function test_buildEthCallRequestBytes() public { + bytes memory ecr = QueryTest.buildEthCallRequestBytes( + /* blockId */ "0x744", + /* numCallData */ 2, + /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" + ); + assertEq(ecr, hex"00000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } + + function test_buildEthCallByTimestampRequestBytes() public { + bytes memory ecr = QueryTest.buildEthCallByTimestampRequestBytes( + /* targetTimeUs */ 0x10642ac0, + /* targetBlockHint */ "0x15d", + /* followingBlockHint */ "0x15e", + /* numCallData */ 2, + /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" + ); + assertEq(ecr, hex"0000000010642ac000000005307831356400000005307831356502ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } + + function test_buildEthCallWithFinalityRequestBytes() public { + bytes memory ecr = QueryTest.buildEthCallWithFinalityRequestBytes( + /* blockId */ "0x1f8", + /* finality */ "finalized", + /* numCallData */ 2, + /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" + ); + assertEq(ecr, hex"0000000530783166380000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } + + function test_buildEthCallRecordBytes() public { + bytes memory ecd1 = QueryTest.buildEthCallRecordBytes( + /* contractAddress */ 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E, + /* callData */ hex"06fdde03" + ); + assertEq(ecd1, hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03"); + bytes memory ecd2 = QueryTest.buildEthCallRecordBytes( + /* contractAddress */ 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E, + /* callData */ hex"313ce567" + ); + assertEq(ecd2, hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + } - function test_buildEthCallWithFinalityRequestBytes() public { - bytes memory ecr = QueryTest.buildEthCallWithFinalityRequestBytes( - /* blockId */ "0x1f8", - /* finality */ "finalized", - /* numCallData */ 2, - /* callData */ hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567" - ); - assertEq(ecr, hex"0000000530783166380000000966696e616c697a656402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); + function test_buildSolanaAccountRequestBytes() public { + bytes memory ecr = QueryTest.buildSolanaAccountRequestBytes( + /* commitment */ "finalized", + /* minContextSlot */ 8069, + /* dataSliceOffset */ 10, + /* dataSliceLength */ 20, + /* numAccounts */ 2, + /* accounts */ hex"165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7" + ); + assertEq(ecr, hex"0000000966696e616c697a65640000000000001f85000000000000000a000000000000001402165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"); + } + + function test_buildSolanaPdaRequestBytes() public { + bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; + bytes[] memory pdas = new bytes[](2); + + bytes[] memory seeds = new bytes[](2); + seeds[0] = hex"477561726469616e536574"; + seeds[1] = hex"00000000"; + (bytes memory seedBytes, uint8 numSeeds) = QueryTest.buildSolanaPdaSeedBytes(seeds); + assertEq(seedBytes, hex"0000000b477561726469616e5365740000000400000000"); + + pdas[0] = QueryTest.buildSolanaPdaEntry( + programId, + numSeeds, + seedBytes + ); + assertEq(pdas[0], hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"); + assertEq(numSeeds, uint8(seeds.length)); + + bytes[] memory seeds2 = new bytes[](2); + seeds2[0] = hex"477561726469616e536574"; + seeds2[1] = hex"00000001"; + (bytes memory seedBytes2, uint8 numSeeds2) = QueryTest.buildSolanaPdaSeedBytes(seeds2); + assertEq(seedBytes2, hex"0000000b477561726469616e5365740000000400000001"); + + pdas[1] = QueryTest.buildSolanaPdaEntry( + programId, + numSeeds2, + seedBytes2 + ); + assertEq(pdas[1], hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000001"); + assertEq(numSeeds2, uint8(seeds2.length)); + + bytes memory ecr = QueryTest.buildSolanaPdaRequestBytes( + /* commitment */ "finalized", + /* minContextSlot */ 2303, + /* dataSliceOffset */ 12, + /* dataSliceLength */ 20, + /* pdas */ pdas + ); + assertEq(ecr, hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140202c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e536574000000040000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000001"); + } + + function test_buildSolanaPdaRequestBytesTooManyPDAs() public { + bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; + bytes[] memory pdas = new bytes[](256); + + uint numPDAs = pdas.length; + for (uint idx; idx < numPDAs;) { + bytes[] memory seeds = new bytes[](2); + seeds[0] = hex"477561726469616e536574"; + seeds[1] = hex"00000000"; + (bytes memory seedBytes, uint8 numSeeds) = QueryTest.buildSolanaPdaSeedBytes(seeds); + + pdas[idx] = QueryTest.buildSolanaPdaEntry( + programId, + numSeeds, + seedBytes + ); + + unchecked { ++idx; } } - function test_buildEthCallDataBytes() public { - bytes memory ecd1 = QueryTest.buildEthCallDataBytes( - /* contractAddress */ 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E, - /* callData */ hex"06fdde03" - ); - assertEq(ecd1, hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03"); - bytes memory ecd2 = QueryTest.buildEthCallDataBytes( - /* contractAddress */ 0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E, - /* callData */ hex"313ce567" - ); - assertEq(ecd2, hex"ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567"); - } - - function test_buildSolanaAccountRequestBytes() public { - bytes memory ecr = QueryTest.buildSolanaAccountRequestBytes( - /* commitment */ "finalized", - /* minContextSlot */ 8069, - /* dataSliceOffset */ 10, - /* dataSliceLength */ 20, - /* numAccounts */ 2, - /* accounts */ hex"165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7" - ); - assertEq(ecr, hex"0000000966696e616c697a65640000000000001f85000000000000000a000000000000001402165809739240a0ac03b98440fe8985548e3aa683cd0d4d9df5b5659669faa3019c006c48c8cbf33849cb07a3f936159cc523f9591cb1999abd45890ec5fee9b7"); + vm.expectRevert(QueryTest.SolanaTooManyPDAs.selector); + QueryTest.buildSolanaPdaRequestBytes( + /* commitment */ "finalized", + /* minContextSlot */ 2303, + /* dataSliceOffset */ 12, + /* dataSliceLength */ 20, + /* pdas */ pdas + ); + } + + function test_buildSolanaPdaEntryTooManySeeds() public { + bytes[] memory seeds = new bytes[](2); + seeds[0] = hex"477561726469616e536574"; + seeds[1] = hex"00000000"; + (bytes memory seedBytes,) = QueryTest.buildSolanaPdaSeedBytes(seeds); + assertEq(seedBytes, hex"0000000b477561726469616e5365740000000400000000"); + + bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; + + vm.expectRevert(QueryTest.SolanaTooManySeeds.selector); + QueryTest.buildSolanaPdaEntry( + programId, + uint8(QueryTest.SOLANA_MAX_SEEDS + 1), + seedBytes + ); + } + + function test_buildSolanaPdaSeedBytesTooManySeeds() public { + bytes[] memory seeds = new bytes[](QueryTest.SOLANA_MAX_SEEDS + 1); + uint numSeeds = seeds.length; + for (uint idx; idx < numSeeds;) { + seeds[idx] = "junk"; + unchecked { ++idx; } } - function test_buildSolanaPdaRequestBytes() public { - bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; - bytes[] memory pdas = new bytes[](2); - - bytes[] memory seeds = new bytes[](2); - seeds[0] = hex"477561726469616e536574"; - seeds[1] = hex"00000000"; - (bytes memory seedBytes, uint8 numSeeds) = QueryTest.buildSolanaPdaSeedBytes(seeds); - assertEq(seedBytes, hex"0000000b477561726469616e5365740000000400000000"); - - pdas[0] = QueryTest.buildSolanaPdaEntry( - programId, - numSeeds, - seedBytes - ); - assertEq(pdas[0], hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000000"); - assertEq(numSeeds, uint8(seeds.length)); - - bytes[] memory seeds2 = new bytes[](2); - seeds2[0] = hex"477561726469616e536574"; - seeds2[1] = hex"00000001"; - (bytes memory seedBytes2, uint8 numSeeds2) = QueryTest.buildSolanaPdaSeedBytes(seeds2); - assertEq(seedBytes2, hex"0000000b477561726469616e5365740000000400000001"); - - pdas[1] = QueryTest.buildSolanaPdaEntry( - programId, - numSeeds2, - seedBytes2 - ); - assertEq(pdas[1], hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000001"); - assertEq(numSeeds2, uint8(seeds2.length)); - - bytes memory ecr = QueryTest.buildSolanaPdaRequestBytes( - /* commitment */ "finalized", - /* minContextSlot */ 2303, - /* dataSliceOffset */ 12, - /* dataSliceLength */ 20, - /* pdas */ pdas - ); - assertEq(ecr, hex"0000000966696e616c697a656400000000000008ff000000000000000c00000000000000140202c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e536574000000040000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa020000000b477561726469616e5365740000000400000001"); - } + vm.expectRevert(QueryTest.SolanaTooManySeeds.selector); + QueryTest.buildSolanaPdaSeedBytes(seeds); + } - function test_buildSolanaPdaRequestBytesTooManyPDAs() public { - bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; - bytes[] memory pdas = new bytes[](256); - - uint numPDAs = pdas.length; - for (uint idx; idx < numPDAs;) { - bytes[] memory seeds = new bytes[](2); - seeds[0] = hex"477561726469616e536574"; - seeds[1] = hex"00000000"; - (bytes memory seedBytes, uint8 numSeeds) = QueryTest.buildSolanaPdaSeedBytes(seeds); - - pdas[idx] = QueryTest.buildSolanaPdaEntry( - programId, - numSeeds, - seedBytes - ); - - unchecked { ++idx; } - } - - vm.expectRevert(QueryTest.SolanaTooManyPDAs.selector); - QueryTest.buildSolanaPdaRequestBytes( - /* commitment */ "finalized", - /* minContextSlot */ 2303, - /* dataSliceOffset */ 12, - /* dataSliceLength */ 20, - /* pdas */ pdas - ); - } - - function test_buildSolanaPdaEntryTooManySeeds() public { - bytes[] memory seeds = new bytes[](2); - seeds[0] = hex"477561726469616e536574"; - seeds[1] = hex"00000000"; - (bytes memory seedBytes,) = QueryTest.buildSolanaPdaSeedBytes(seeds); - assertEq(seedBytes, hex"0000000b477561726469616e5365740000000400000000"); - - bytes32 programId = hex"02c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa"; - - vm.expectRevert(QueryTest.SolanaTooManySeeds.selector); - QueryTest.buildSolanaPdaEntry( - programId, - uint8(QueryTest.SolanaMaxSeeds + 1), - seedBytes - ); - } - - function test_buildSolanaPdaSeedBytesTooManySeeds() public { - bytes[] memory seeds = new bytes[](QueryTest.SolanaMaxSeeds + 1); - uint numSeeds = seeds.length; - for (uint idx; idx < numSeeds;) { - seeds[idx] = "junk"; - unchecked { ++idx; } - } + function test_buildSolanaPdaSeedBytesSeedTooLong() public { + bytes[] memory seeds = new bytes[](2); + seeds[0] = "junk"; + seeds[1] = "This seed is too long!!!!!!!!!!!!"; - vm.expectRevert(QueryTest.SolanaTooManySeeds.selector); - QueryTest.buildSolanaPdaSeedBytes(seeds); - } - - function test_buildSolanaPdaSeedBytesSeedTooLong() public { - bytes[] memory seeds = new bytes[](2); - seeds[0] = "junk"; - seeds[1] = "This seed is too long!!!!!!!!!!!!"; - - vm.expectRevert(QueryTest.SolanaSeedTooLong.selector); - QueryTest.buildSolanaPdaSeedBytes(seeds); - } - - // - // Query Response tests - // - - function test_buildQueryResponseBytes() public { - bytes memory resp = QueryTest.buildQueryResponseBytes( - /* version */ 1, - /* senderChainId */ 0, - /* signature */ hex"11b03bdbbe15a8f12b803d2193de5ddff72d92eaabd2763553ec3c3133182d1443719a05e2b65c87b923c6bd8aeff49f34937f90f3ab7cd33449388c60fa30a301", - /* queryRequest */ hex"0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567", - /* numPerChainResponses */ 1, - /* perChainResponses */ hex"000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(resp, hex"01000011b03bdbbe15a8f12b803d2193de5ddff72d92eaabd2763553ec3c3133182d1443719a05e2b65c87b923c6bd8aeff49f34937f90f3ab7cd33449388c60fa30a3010000004f0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce56701000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildPerChainResponseBytes() public { - bytes memory pcr = QueryTest.buildPerChainResponseBytes( - /* chainId */ 2, - /* queryType */ 1, - /* responseBytes */ hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(pcr, hex"000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildEthCallResponseBytes() public { - bytes memory ecr = QueryTest.buildEthCallResponseBytes( - /* blockNumber */ 1860, - /* blockHash */ hex"6a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b", - /* blockTimeUs */ 0x6ab13b80, - /* numResults */ 2, - /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(ecr, hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildEthCallByTimestampResponseBytes() public { - bytes memory ecr = QueryTest.buildEthCallByTimestampResponseBytes( - /* targetBlockNumber */ 349, - /* targetBlockHash */ hex"966cd846f812be43c4ee2d310f962bc592ba944c66de878e53584b8e75c6051f", - /* targetBlockTimeUs */ 0x10642ac0, - /* followingBlockNumber */ 350, - /* followingBlockHash */ hex"04b022afaab8da2dd80bd8e6ae55e6303473a5e1de846a5de76d619e162429ce", - /* followingBlockTimeUs */ 0x10736d00, - /* numResults */ 2, - /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(ecr, hex"000000000000015d966cd846f812be43c4ee2d310f962bc592ba944c66de878e53584b8e75c6051f0000000010642ac0000000000000015e04b022afaab8da2dd80bd8e6ae55e6303473a5e1de846a5de76d619e162429ce0000000010736d0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildEthCallWithFinalityResponseBytes() public { - bytes memory ecr = QueryTest.buildEthCallWithFinalityResponseBytes( - /* blockNumber */ 1860, - /* blockHash */ hex"6a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b", - /* blockTimeUs */ 0x6ab13b80, - /* numResults */ 2, - /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(ecr, hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildEthCallResultBytes() public { - bytes memory ecr1 = QueryTest.buildEthCallResultBytes( - /* result */ hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000" - ); - assertEq(ecr1, hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); - bytes memory ecr2 = QueryTest.buildEthCallResultBytes( - /* result */ hex"0000000000000000000000000000000000000000000000000000000000000012" - ); - assertEq(ecr2, hex"000000200000000000000000000000000000000000000000000000000000000000000012"); - } - - function test_buildSolanaAccountResponseBytes() public { - bytes memory ecr = QueryTest.buildSolanaAccountResponseBytes( - /* slotNumber */ 5603, - /* blockTimeUs */ 0x610cdf2510500, - /* blockHash */ hex"e0eca895a92c0347e30538cd07c50777440de58e896dd13ff86ef0dae3e12552", - /* numResults */ 2, - /* results */ hex"0000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000" - ); - assertEq(ecr, hex"00000000000015e3000610cdf2510500e0eca895a92c0347e30538cd07c50777440de58e896dd13ff86ef0dae3e12552020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"); - } + vm.expectRevert(QueryTest.SolanaSeedTooLong.selector); + QueryTest.buildSolanaPdaSeedBytes(seeds); + } +} - function test_buildSolanaPdaResponseBytes() public { - bytes memory ecr = QueryTest.buildSolanaPdaResponseBytes( - /* slotNumber */ 2303, - /* blockTimeUs */ 0x6115e3f6d7540, - /* blockHash */ hex"e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b", - /* numResults */ 1, - /* results */ hex"4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65" - ); - assertEq(ecr, hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"); - } +contract QueryResponseTest is Test { + function test_buildQueryResponseBytes() public { + bytes memory resp = QueryTest.buildQueryResponseBytes( + /* version */ 1, + /* senderChainId */ 0, + /* signature */ hex"11b03bdbbe15a8f12b803d2193de5ddff72d92eaabd2763553ec3c3133182d1443719a05e2b65c87b923c6bd8aeff49f34937f90f3ab7cd33449388c60fa30a301", + /* queryRequest */ hex"0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce567", + /* numPerChainResponses */ 1, + /* perChainResponses */ hex"000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(resp, hex"01000011b03bdbbe15a8f12b803d2193de5ddff72d92eaabd2763553ec3c3133182d1443719a05e2b65c87b923c6bd8aeff49f34937f90f3ab7cd33449388c60fa30a3010000004f0100000001010002010000004200000005307837343402ddb64fe46a91d46ee29420539fc25fd07c5fea3e0000000406fdde03ddb64fe46a91d46ee29420539fc25fd07c5fea3e00000004313ce56701000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildPerChainResponseBytes() public { + bytes memory pcr = QueryTest.buildPerChainResponseBytes( + /* chainId */ 2, + /* queryType */ 1, + /* responseBytes */ hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(pcr, hex"000201000000b900000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildEthCallResponseBytes() public { + bytes memory ecr = QueryTest.buildEthCallResponseBytes( + /* blockNumber */ 1860, + /* blockHash */ hex"6a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b", + /* blockTimeUs */ 0x6ab13b80, + /* numResults */ 2, + /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(ecr, hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildEthCallByTimestampResponseBytes() public { + bytes memory ecr = QueryTest.buildEthCallByTimestampResponseBytes( + /* targetBlockNumber */ 349, + /* targetBlockHash */ hex"966cd846f812be43c4ee2d310f962bc592ba944c66de878e53584b8e75c6051f", + /* targetBlockTimeUs */ 0x10642ac0, + /* followingBlockNumber */ 350, + /* followingBlockHash */ hex"04b022afaab8da2dd80bd8e6ae55e6303473a5e1de846a5de76d619e162429ce", + /* followingBlockTimeUs */ 0x10736d00, + /* numResults */ 2, + /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(ecr, hex"000000000000015d966cd846f812be43c4ee2d310f962bc592ba944c66de878e53584b8e75c6051f0000000010642ac0000000000000015e04b022afaab8da2dd80bd8e6ae55e6303473a5e1de846a5de76d619e162429ce0000000010736d0002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildEthCallWithFinalityResponseBytes() public { + bytes memory ecr = QueryTest.buildEthCallWithFinalityResponseBytes( + /* blockNumber */ 1860, + /* blockHash */ hex"6a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b", + /* blockTimeUs */ 0x6ab13b80, + /* numResults */ 2, + /* results */ hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(ecr, hex"00000000000007446a0b819aee8945e659e37537a0bdbe03c06275be23e499819138d1eee8337e9b000000006ab13b8002000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildEthCallResultBytes() public { + bytes memory ecr1 = QueryTest.buildEthCallResultBytes( + /* result */ hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000" + ); + assertEq(ecr1, hex"000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5772617070656420457468657200000000000000000000000000000000000000"); + bytes memory ecr2 = QueryTest.buildEthCallResultBytes( + /* result */ hex"0000000000000000000000000000000000000000000000000000000000000012" + ); + assertEq(ecr2, hex"000000200000000000000000000000000000000000000000000000000000000000000012"); + } + + function test_buildSolanaAccountResponseBytes() public { + bytes memory ecr = QueryTest.buildSolanaAccountResponseBytes( + /* slotNumber */ 5603, + /* blockTimeUs */ 0x610cdf2510500, + /* blockHash */ hex"e0eca895a92c0347e30538cd07c50777440de58e896dd13ff86ef0dae3e12552", + /* numResults */ 2, + /* results */ hex"0000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000" + ); + assertEq(ecr, hex"00000000000015e3000610cdf2510500e0eca895a92c0347e30538cd07c50777440de58e896dd13ff86ef0dae3e12552020000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d0000e8890423c78a09010000000000000000000000000000000000000000000000000000000000000000000000000000000000164d6000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90000005201000000574108aed69daf7e625a361864b1f74d13702f2ca56de9660e566d1d8691848d01000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000"); + } + + function test_buildSolanaPdaResponseBytes() public { + bytes memory ecr = QueryTest.buildSolanaPdaResponseBytes( + /* slotNumber */ 2303, + /* blockTimeUs */ 0x6115e3f6d7540, + /* blockHash */ hex"e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b", + /* numResults */ 1, + /* results */ hex"4fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65" + ); + assertEq(ecr, hex"00000000000008ff0006115e3f6d7540e05035785e15056a8559815e71343ce31db2abf23f65b19c982b68aee7bf207b014fa9188b339cfd573a0778c5deaeeee94d4bcfb12b345bf8e417e5119dae773efd0000000000116ac000000000000000000002c806312cbe5b79ef8aa6c17e3f423d8fdfe1d46909fb1f6cdf65ee8e2e6faa0000001457cd18b7f8a4d91a2da9ab4af05d0fbece2dcd65"); + } }