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:
parent
1b250a8091
commit
af8138d4fb
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue