-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathquery-demo.sol
137 lines (109 loc) · 4.83 KB
/
query-demo.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// contracts/query/QueryDemo.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
import "wormhole-solidity-sdk/interfaces/IWormhole.sol";
import "wormhole-solidity-sdk/QueryResponse.sol";
error InvalidOwner();
// @dev for the onlyOwner modifier
error InvalidCaller();
error InvalidCalldata();
error InvalidForeignChainID();
error ObsoleteUpdate();
error StaleUpdate();
error UnexpectedResultLength();
error UnexpectedResultMismatch();
/// @dev QueryDemo is an example of using the QueryResponse library to parse and verify Cross Chain Query (CCQ) responses.
contract QueryDemo is QueryResponse {
using BytesParsing for bytes;
struct ChainEntry {
uint16 chainID;
address contractAddress;
uint256 counter;
uint256 blockNum;
uint256 blockTime;
}
address private immutable owner;
uint16 private immutable myChainID;
mapping(uint16 => ChainEntry) private counters;
uint16[] private foreignChainIDs;
bytes4 public GetMyCounter = bytes4(hex"916d5743");
constructor(address _owner, address _wormhole, uint16 _myChainID) QueryResponse(_wormhole) {
if (_owner == address(0)) {
revert InvalidOwner();
}
owner = _owner;
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];
uint256 length = foreignChainIDs.length;
for (uint256 i = 0; i < length;) {
ret[i + 1] = counters[foreignChainIDs[i]];
unchecked {
++i;
}
}
return ret;
}
// @notice Takes the cross chain query response for the 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 {
ParsedQueryResponse memory r = parseAndVerifyQueryResponse(response, signatures);
uint256 numResponses = r.responses.length;
if (numResponses != foreignChainIDs.length) {
revert UnexpectedResultLength();
}
for (uint256 i = 0; i < numResponses;) {
// Create a storage pointer for frequently read and updated data stored on the blockchain
ChainEntry storage chainEntry = counters[r.responses[i].chainId];
if (chainEntry.chainID != foreignChainIDs[i]) {
revert InvalidForeignChainID();
}
EthCallQueryResponse memory eqr = parseEthCallQueryResponse(r.responses[i]);
// Validate that update is not obsolete
validateBlockNum(eqr.blockNum, chainEntry.blockNum);
// Validate that update is not stale
validateBlockTime(eqr.blockTime, block.timestamp - 300);
if (eqr.result.length != 1) {
revert UnexpectedResultMismatch();
}
// Validate addresses and function signatures
address[] memory validAddresses = new address[](1);
bytes4[] memory validFunctionSignatures = new bytes4[](1);
validAddresses[0] = chainEntry.contractAddress;
validFunctionSignatures[0] = GetMyCounter;
validateMultipleEthCallData(eqr.result, validAddresses, validFunctionSignatures);
require(eqr.result[0].result.length == 32, "result is not a uint256");
chainEntry.blockNum = eqr.blockNum;
chainEntry.blockTime = eqr.blockTime / 1_000_000;
chainEntry.counter = abi.decode(eqr.result[0].result, (uint256));
unchecked {
++i;
}
}
counters[myChainID].blockNum = block.number;
counters[myChainID].blockTime = block.timestamp;
counters[myChainID].counter += 1;
}
modifier onlyOwner() {
if (owner != msg.sender) {
revert InvalidOwner();
}
_;
}
}