CCQ/EVM: eth_call_by_timestamp and eth_call_with_finality support (#3468)

* CCQ/EVM: ethCallByTimestamp & ethCallWithFinality

* Code rework

* Code review rework
This commit is contained in:
bruce-riley 2023-10-31 16:05:19 -05:00 committed by GitHub
parent 1b250a8091
commit af8138d4fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 213 additions and 1 deletions

View File

@ -32,6 +32,30 @@ struct EthCallQueryResponse {
EthCallData [] result;
}
// @dev EthCallByTimestampQueryResponse describes 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;
}
// @dev EthCallWithFinalityQueryResponse describes an ETH call with finality per-chain query.
struct EthCallWithFinalityQueryResponse {
bytes requestBlockId;
bytes requestFinality;
uint64 blockNum;
uint64 blockTime;
bytes32 blockHash;
EthCallData [] result;
}
// @dev EthCallData describes a single ETH call query / response pair.
struct EthCallData {
address contractAddress;
@ -56,6 +80,9 @@ abstract contract QueryResponse {
bytes public constant responsePrefix = bytes("query_response_0000000000000000000|");
uint8 public constant VERSION = 1;
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_MAX = 4; // Keep this last
/// @dev getResponseHash computes the hash of the specified query response.
function getResponseHash(bytes memory response) public pure returns (bytes32) {
@ -129,7 +156,7 @@ abstract contract QueryResponse {
revert RequestTypeMismatch();
}
if (r.responses[idx].queryType != QT_ETH_CALL) {
if (r.responses[idx].queryType < QT_ETH_CALL || r.responses[idx].queryType >= QT_MAX) {
revert UnsupportedQueryType();
}
@ -195,6 +222,110 @@ abstract contract QueryResponse {
return r;
}
/// @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 UnsupportedQueryType();
}
uint reqIdx = 0;
uint respIdx = 0;
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
(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
(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
uint8 respNumResults;
(respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results
if (respNumResults != numBatchCallData) {
revert UnexpectedNumberOfResults();
}
r.result = new EthCallData[](numBatchCallData);
// Walk through the call data and results in lock step.
for (uint idx = 0; 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);
}
/// @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 UnsupportedQueryType();
}
uint reqIdx = 0;
uint respIdx = 0;
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
(r.blockNum, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_number
(r.blockHash, respIdx) = pcr.response.asBytes32Unchecked(respIdx); // Response block_hash
(r.blockTime, respIdx) = pcr.response.asUint64Unchecked(respIdx); // Response block_time_us
uint8 respNumResults;
(respNumResults, respIdx) = pcr.response.asUint8Unchecked(respIdx); // Response num_results
if (respNumResults != numBatchCallData) {
revert UnexpectedNumberOfResults();
}
r.result = new EthCallData[](numBatchCallData);
// Walk through the call data and results in lock step.
for (uint idx = 0; 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);
}
/**
* @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

View File

@ -159,6 +159,87 @@ contract TestQueryResponse is Test {
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(UnsupportedQueryType.selector);
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(UnsupportedQueryType.selector);
queryResponse.parseEthCallWithFinalityQueryResponse(r);
}
}