wormhole/ethereum/contracts/query/QueryDemo.sol

102 lines
4.3 KiB
Solidity

// contracts/query/QueryDemo.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "../libraries/external/BytesLib.sol";
import "../interfaces/IWormhole.sol";
import "./QueryResponse.sol";
/// @dev QueryDemo is a library that implements the parsing and verification of Cross Chain Query (CCQ) responses.
contract QueryDemo is QueryResponse {
using BytesLib for bytes;
struct ChainEntry {
uint16 chainID;
address contractAddress;
uint256 counter;
uint256 blockNum;
uint256 blockTime;
}
address private immutable owner;
address private immutable wormhole;
uint16 private immutable myChainID;
mapping(uint16 => ChainEntry) private counters;
uint16[] private foreignChainIDs;
bytes4 GetMyCounter = bytes4(hex"916d5743");
constructor(address _owner, address _wormhole, uint16 _myChainID) {
owner = _owner;
wormhole = _wormhole;
myChainID = _myChainID;
counters[_myChainID] = ChainEntry(_myChainID, address(this), 0, 0, 0);
}
// updateRegistration should be used to add the other chains and to set / update contract addresses.
function updateRegistration(uint16 _chainID, address _contractAddress) public onlyOwner {
if (counters[_chainID].chainID == 0) {
foreignChainIDs.push(_chainID);
counters[_chainID].chainID = _chainID;
}
counters[_chainID].contractAddress = _contractAddress;
}
// getMyCounter (call signature 916d5743) returns the counter value for this chain. It is meant to be used in a cross chain query.
function getMyCounter() public view returns (uint256) {
return counters[myChainID].counter;
}
// getState() returns this chain's view of all the counters. It is meant to be used in the front end.
function getState() public view returns (ChainEntry[] memory) {
ChainEntry[] memory ret = new ChainEntry[](foreignChainIDs.length + 1);
ret[0] = counters[myChainID];
for (uint idx=0; idx<foreignChainIDs.length; idx++) {
ret[idx+1] = counters[foreignChainIDs[idx]];
}
return ret;
}
// updateCounters takes the cross chain query response for the two other counters, stores the results for the other chains, and updates the counter for this chain.
function updateCounters(bytes memory response, IWormhole.Signature[] memory signatures) public {
uint256 adjustedBlockTime;
ParsedQueryResponse memory r = parseAndVerifyQueryResponse(address(wormhole), response, signatures);
require(r.responses.length == foreignChainIDs.length, "unexpected number of results");
for (uint idx=0; idx<r.responses.length; idx++) {
require(counters[r.responses[idx].chainId].chainID == foreignChainIDs[idx], "unexpected foreign chain ID");
EthCallQueryResponse memory eqr = parseEthCallQueryResponse(r.responses[idx]);
require(eqr.blockNum > counters[r.responses[idx].chainId].blockNum, "update is obsolete");
// wormhole time is in microseconds, timestamp is in seconds
adjustedBlockTime = eqr.blockTime / 1_000_000;
require(adjustedBlockTime > block.timestamp - 300, "update is stale");
require(eqr.result.length == 1, "result mismatch");
require(eqr.result[0].contractAddress == counters[r.responses[idx].chainId].contractAddress, "contract address is wrong");
// TODO: Is there an easier way to verify that the call data is correct!
bytes memory callData = eqr.result[0].callData;
bytes4 result;
assembly {
result := mload(add(callData, 32))
}
require(result == GetMyCounter, "unexpected callData");
require(eqr.result[0].result.length == 32, "result is not a uint256");
counters[r.responses[idx].chainId].blockNum = eqr.blockNum;
counters[r.responses[idx].chainId].blockTime = adjustedBlockTime;
counters[r.responses[idx].chainId].counter = abi.decode(eqr.result[0].result, (uint256));
}
counters[myChainID].blockNum = block.number;
counters[myChainID].blockTime = block.timestamp;
counters[myChainID].counter += 1;
}
modifier onlyOwner() {
require(owner == msg.sender, "caller is not the owner");
_;
}
}