102 lines
4.3 KiB
Solidity
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");
|
|
_;
|
|
}
|
|
}
|