// 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 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"); _; } }