@wormhole-foundation/wormhole-solidity-sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +78 -0
- package/contracts/Executor/Integration.sol +180 -0
- package/contracts/Executor/RelayInstruction.sol +108 -0
- package/contracts/Executor/Request.sol +43 -0
- package/contracts/RawDispatcher.sol +29 -0
- package/contracts/Utils.sol +27 -0
- package/contracts/WormholeRelayer/AdditionalMessages.sol +37 -0
- package/contracts/WormholeRelayer/Keys.sol +198 -0
- package/contracts/WormholeRelayer/Receiver.sol +44 -0
- package/contracts/WormholeRelayer/Sender.sol +264 -0
- package/contracts/WormholeRelayer.sol +7 -0
- package/contracts/constants/CctpDomainMapping.sol +72 -0
- package/contracts/constants/CctpDomains.sol +21 -0
- package/contracts/constants/Chains.sol +66 -0
- package/contracts/constants/Common.sol +17 -0
- package/contracts/constants/ConsistencyLevel.sol +9 -0
- package/contracts/interfaces/ICoreBridge.sol +80 -0
- package/contracts/interfaces/ICustomConsistencyLevel.sol +12 -0
- package/contracts/interfaces/IDeliveryProvider.sol +84 -0
- package/contracts/interfaces/IExecutor.sol +32 -0
- package/contracts/interfaces/ITokenBridge.sol +81 -0
- package/contracts/interfaces/IWormholeRelayer.sol +658 -0
- package/contracts/interfaces/cctp/IMessageTransmitter.sol +76 -0
- package/contracts/interfaces/cctp/ITokenMessenger.sol +72 -0
- package/contracts/interfaces/cctp/ITokenMinter.sol +40 -0
- package/contracts/interfaces/cctp/shared/IOwnable2Step.sol +18 -0
- package/contracts/interfaces/cctp/shared/IPausable.sol +20 -0
- package/contracts/interfaces/token/IERC20.sol +16 -0
- package/contracts/interfaces/token/IERC20Metadata.sol +11 -0
- package/contracts/interfaces/token/IERC20Permit.sol +19 -0
- package/contracts/interfaces/token/IWETH.sol +12 -0
- package/contracts/libraries/BytesParsing.sol +2848 -0
- package/contracts/libraries/CctpMessages.sol +755 -0
- package/contracts/libraries/CoreBridge.sol +365 -0
- package/contracts/libraries/CustomConsistency.sol +60 -0
- package/contracts/libraries/Percentage.sol +99 -0
- package/contracts/libraries/PermitParsing.sol +580 -0
- package/contracts/libraries/QueryResponse.sol +741 -0
- package/contracts/libraries/ReplayProtection.sol +106 -0
- package/contracts/libraries/SafeERC20.sol +67 -0
- package/contracts/libraries/TokenBridgeMessages.sol +742 -0
- package/contracts/libraries/TypedUnits.sol +304 -0
- package/contracts/libraries/UncheckedIndexing.sol +99 -0
- package/contracts/libraries/VaaLib.sol +1385 -0
- package/contracts/proxy/Eip1967Admin.sol +20 -0
- package/contracts/proxy/Eip1967Implementation.sol +15 -0
- package/contracts/proxy/Proxy.sol +43 -0
- package/contracts/proxy/ProxyBase.sol +80 -0
- package/contracts/utils/DecimalNormalization.sol +18 -0
- package/contracts/utils/EagerOps.sol +18 -0
- package/contracts/utils/Keccak.sol +39 -0
- package/contracts/utils/Revert.sol +11 -0
- package/contracts/utils/Transfer.sol +23 -0
- package/contracts/utils/UniversalAddress.sol +17 -0
- package/package.json +23 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.4;
|
|
3
|
+
|
|
4
|
+
import {BytesParsing} from "./BytesParsing.sol";
|
|
5
|
+
import {CoreBridgeLib} from "./CoreBridge.sol";
|
|
6
|
+
import {GuardianSignature} from "./VaaLib.sol";
|
|
7
|
+
import {eagerAnd, eagerOr, keccak256Cd} from "../Utils.sol";
|
|
8
|
+
|
|
9
|
+
library QueryType {
|
|
10
|
+
error UnsupportedQueryType(uint8 received);
|
|
11
|
+
|
|
12
|
+
//Solidity enums don't permit custom values (i.e. can't start from 1)
|
|
13
|
+
//Also invalid enum conversions result in panics and manual range checking requires assembly
|
|
14
|
+
// to avoid superfluous double checking.
|
|
15
|
+
//So we're sticking with uint8 constants instead.
|
|
16
|
+
uint8 internal constant ETH_CALL = 1;
|
|
17
|
+
uint8 internal constant ETH_CALL_BY_TIMESTAMP = 2;
|
|
18
|
+
uint8 internal constant ETH_CALL_WITH_FINALITY = 3;
|
|
19
|
+
uint8 internal constant SOLANA_ACCOUNT = 4;
|
|
20
|
+
uint8 internal constant SOLANA_PDA = 5;
|
|
21
|
+
|
|
22
|
+
//emulate type(enum).min/max for external consumers (mainly tests)
|
|
23
|
+
function min() internal pure returns (uint8) { return ETH_CALL; }
|
|
24
|
+
function max() internal pure returns (uint8) { return SOLANA_PDA; }
|
|
25
|
+
|
|
26
|
+
function checkValid(uint8 queryType) internal pure {
|
|
27
|
+
//slightly more gas efficient than calling `isValid`
|
|
28
|
+
if (eagerOr(queryType == 0, queryType > SOLANA_PDA))
|
|
29
|
+
revert UnsupportedQueryType(queryType);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isValid(uint8 queryType) internal pure returns (bool) {
|
|
33
|
+
//see docs/Optimization.md why `< CONST + 1` rather than `<= CONST`
|
|
34
|
+
//see docs/Optimization.md for rationale behind `eagerAnd`
|
|
35
|
+
return eagerAnd(queryType > 0, queryType < SOLANA_PDA + 1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct QueryResponse {
|
|
40
|
+
uint8 version;
|
|
41
|
+
uint16 senderChainId;
|
|
42
|
+
uint32 nonce;
|
|
43
|
+
bytes requestId; // 65 byte sig for off-chain, 32 byte vaaHash for on-chain
|
|
44
|
+
PerChainQueryResponse[] responses;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
struct PerChainQueryResponse {
|
|
48
|
+
uint16 chainId;
|
|
49
|
+
uint8 queryType;
|
|
50
|
+
bytes request;
|
|
51
|
+
bytes response;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct EthCallQueryResponse {
|
|
55
|
+
bytes requestBlockId;
|
|
56
|
+
uint64 blockNum;
|
|
57
|
+
uint64 blockTime;
|
|
58
|
+
bytes32 blockHash;
|
|
59
|
+
EthCallRecord[] results;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
struct EthCallByTimestampQueryResponse {
|
|
63
|
+
bytes requestTargetBlockIdHint;
|
|
64
|
+
bytes requestFollowingBlockIdHint;
|
|
65
|
+
uint64 requestTargetTimestamp;
|
|
66
|
+
uint64 targetBlockNum;
|
|
67
|
+
uint64 targetBlockTime;
|
|
68
|
+
uint64 followingBlockNum;
|
|
69
|
+
bytes32 targetBlockHash;
|
|
70
|
+
bytes32 followingBlockHash;
|
|
71
|
+
uint64 followingBlockTime;
|
|
72
|
+
EthCallRecord[] results;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
struct EthCallWithFinalityQueryResponse {
|
|
76
|
+
bytes requestBlockId;
|
|
77
|
+
bytes requestFinality;
|
|
78
|
+
uint64 blockNum;
|
|
79
|
+
uint64 blockTime;
|
|
80
|
+
bytes32 blockHash;
|
|
81
|
+
EthCallRecord[] results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
struct EthCallRecord {
|
|
85
|
+
address contractAddress;
|
|
86
|
+
bytes callData;
|
|
87
|
+
bytes result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct SolanaAccountQueryResponse {
|
|
91
|
+
bytes requestCommitment;
|
|
92
|
+
uint64 requestMinContextSlot;
|
|
93
|
+
uint64 requestDataSliceOffset;
|
|
94
|
+
uint64 requestDataSliceLength;
|
|
95
|
+
uint64 slotNumber;
|
|
96
|
+
uint64 blockTime;
|
|
97
|
+
bytes32 blockHash;
|
|
98
|
+
SolanaAccountResult[] results;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
struct SolanaAccountResult {
|
|
102
|
+
bytes32 account;
|
|
103
|
+
uint64 lamports;
|
|
104
|
+
uint64 rentEpoch;
|
|
105
|
+
bool executable;
|
|
106
|
+
bytes32 owner;
|
|
107
|
+
bytes data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
struct SolanaPdaQueryResponse {
|
|
111
|
+
bytes requestCommitment;
|
|
112
|
+
uint64 requestMinContextSlot;
|
|
113
|
+
uint64 requestDataSliceOffset;
|
|
114
|
+
uint64 requestDataSliceLength;
|
|
115
|
+
uint64 slotNumber;
|
|
116
|
+
uint64 blockTime;
|
|
117
|
+
bytes32 blockHash;
|
|
118
|
+
SolanaPdaResult[] results;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
struct SolanaPdaResult {
|
|
122
|
+
bytes32 programId;
|
|
123
|
+
bytes[] seeds;
|
|
124
|
+
bytes32 account;
|
|
125
|
+
uint64 lamports;
|
|
126
|
+
uint64 rentEpoch;
|
|
127
|
+
bool executable;
|
|
128
|
+
bytes32 owner;
|
|
129
|
+
bytes data;
|
|
130
|
+
uint8 bump;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//QueryResponse is a library that implements the decoding and verification of
|
|
134
|
+
// Cross Chain Query (CCQ) responses.
|
|
135
|
+
//
|
|
136
|
+
//For a detailed discussion of these query responses, please see the white paper:
|
|
137
|
+
// https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0013_ccq.md
|
|
138
|
+
//
|
|
139
|
+
//We only implement Cd and Mem decoding variants for the QueryResponse struct itself because all
|
|
140
|
+
// further decoding will have to operate on the memory bytes anyway since there's no way in plain
|
|
141
|
+
// Solidity to have structs with mixed data location, i.e. a struct in memory that references bytes
|
|
142
|
+
// in calldata.
|
|
143
|
+
// This will at least help cut down the gas cost of decoding/slicing the outer most layer.
|
|
144
|
+
library QueryResponseLib {
|
|
145
|
+
using BytesParsing for bytes;
|
|
146
|
+
|
|
147
|
+
error WrongQueryType(uint8 received, uint8 expected);
|
|
148
|
+
error InvalidResponseVersion();
|
|
149
|
+
error VersionMismatch();
|
|
150
|
+
error ZeroQueries();
|
|
151
|
+
error NumberOfResponsesMismatch();
|
|
152
|
+
error ChainIdMismatch();
|
|
153
|
+
error RequestTypeMismatch();
|
|
154
|
+
error UnexpectedNumberOfResults();
|
|
155
|
+
error InvalidPayloadLength(uint256 received, uint256 expected);
|
|
156
|
+
error InvalidContractAddress();
|
|
157
|
+
error InvalidFunctionSignature();
|
|
158
|
+
error InvalidChainId();
|
|
159
|
+
error StaleBlockNum();
|
|
160
|
+
error StaleBlockTime();
|
|
161
|
+
error VerificationFailed();
|
|
162
|
+
|
|
163
|
+
bytes internal constant RESPONSE_PREFIX = bytes("query_response_0000000000000000000|");
|
|
164
|
+
uint8 internal constant VERSION = 1;
|
|
165
|
+
uint64 internal constant MICROSECONDS_PER_SECOND = 1_000_000;
|
|
166
|
+
|
|
167
|
+
function calcPrefixedResponseHashCd(bytes calldata response) internal pure returns (bytes32) {
|
|
168
|
+
return calcPrefixedResponseHash(keccak256Cd(response));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function calcPrefixedResponseHashMem(bytes memory response) internal pure returns (bytes32) {
|
|
172
|
+
return calcPrefixedResponseHash(keccak256(response));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function calcPrefixedResponseHash(bytes32 responseHash) internal pure returns (bytes32) {
|
|
176
|
+
return keccak256(abi.encodePacked(RESPONSE_PREFIX, responseHash));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// -------- decodeAndVerifyQueryResponse --------
|
|
180
|
+
|
|
181
|
+
// ---- guardian set index variants
|
|
182
|
+
// will look up the guardian set internally and also try to verify against the latest
|
|
183
|
+
// guardian set, if the specified guardian set is expired.
|
|
184
|
+
|
|
185
|
+
function decodeAndVerifyQueryResponseCd(
|
|
186
|
+
address wormhole,
|
|
187
|
+
bytes calldata response,
|
|
188
|
+
GuardianSignature[] calldata guardianSignatures,
|
|
189
|
+
uint32 guardianSetIndex
|
|
190
|
+
) internal view returns (QueryResponse memory ret) {
|
|
191
|
+
verifyQueryResponseCd(wormhole, response, guardianSignatures, guardianSetIndex);
|
|
192
|
+
return decodeQueryResponseCd(response);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function decodeAndVerifyQueryResponseMem(
|
|
196
|
+
address wormhole,
|
|
197
|
+
bytes memory response,
|
|
198
|
+
GuardianSignature[] memory guardianSignatures,
|
|
199
|
+
uint32 guardianSetIndex
|
|
200
|
+
) internal view returns (QueryResponse memory ret) {
|
|
201
|
+
verifyQueryResponseMem(wormhole, response, guardianSignatures, guardianSetIndex);
|
|
202
|
+
return decodeQueryResponseMem(response);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function verifyQueryResponseCd(
|
|
206
|
+
address wormhole,
|
|
207
|
+
bytes calldata response,
|
|
208
|
+
GuardianSignature[] calldata guardianSignatures,
|
|
209
|
+
uint32 guardianSetIndex
|
|
210
|
+
) internal view {
|
|
211
|
+
if (!CoreBridgeLib.isVerifiedByQuorumCd(
|
|
212
|
+
wormhole,
|
|
213
|
+
calcPrefixedResponseHashCd(response),
|
|
214
|
+
guardianSignatures,
|
|
215
|
+
guardianSetIndex
|
|
216
|
+
))
|
|
217
|
+
revert VerificationFailed();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function verifyQueryResponseMem(
|
|
221
|
+
address wormhole,
|
|
222
|
+
bytes memory response,
|
|
223
|
+
GuardianSignature[] memory guardianSignatures,
|
|
224
|
+
uint32 guardianSetIndex
|
|
225
|
+
) internal view {
|
|
226
|
+
if (!CoreBridgeLib.isVerifiedByQuorumMem(
|
|
227
|
+
wormhole,
|
|
228
|
+
calcPrefixedResponseHashMem(response),
|
|
229
|
+
guardianSignatures,
|
|
230
|
+
guardianSetIndex
|
|
231
|
+
))
|
|
232
|
+
revert VerificationFailed();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---- guardian address variants
|
|
236
|
+
// will only try to verify against the specified guardian addresses only
|
|
237
|
+
|
|
238
|
+
function decodeAndVerifyQueryResponseCd(
|
|
239
|
+
bytes calldata response,
|
|
240
|
+
GuardianSignature[] calldata guardianSignatures,
|
|
241
|
+
address[] memory guardians
|
|
242
|
+
) internal pure returns (QueryResponse memory ret) {
|
|
243
|
+
verifyQueryResponseCd(response, guardianSignatures, guardians);
|
|
244
|
+
return decodeQueryResponseCd(response);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function decodeAndVerifyQueryResponseMem(
|
|
248
|
+
bytes memory response,
|
|
249
|
+
GuardianSignature[] memory guardianSignatures,
|
|
250
|
+
address[] memory guardians
|
|
251
|
+
) internal pure returns (QueryResponse memory ret) {
|
|
252
|
+
verifyQueryResponseMem(response, guardianSignatures, guardians);
|
|
253
|
+
return decodeQueryResponseMem(response);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function verifyQueryResponseCd(
|
|
257
|
+
bytes calldata response,
|
|
258
|
+
GuardianSignature[] calldata guardianSignatures,
|
|
259
|
+
address[] memory guardians
|
|
260
|
+
) internal pure {
|
|
261
|
+
if (!CoreBridgeLib.isVerifiedByQuorumCd(
|
|
262
|
+
calcPrefixedResponseHashCd(response),
|
|
263
|
+
guardianSignatures,
|
|
264
|
+
guardians
|
|
265
|
+
))
|
|
266
|
+
revert VerificationFailed();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function verifyQueryResponseMem(
|
|
270
|
+
bytes memory response,
|
|
271
|
+
GuardianSignature[] memory guardianSignatures,
|
|
272
|
+
address[] memory guardians
|
|
273
|
+
) internal pure {
|
|
274
|
+
if (!CoreBridgeLib.isVerifiedByQuorumMem(
|
|
275
|
+
calcPrefixedResponseHashMem(response),
|
|
276
|
+
guardianSignatures,
|
|
277
|
+
guardians
|
|
278
|
+
))
|
|
279
|
+
revert VerificationFailed();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// -------- decode functions --------
|
|
283
|
+
|
|
284
|
+
function decodeQueryResponseCd(
|
|
285
|
+
bytes calldata response
|
|
286
|
+
) internal pure returns (QueryResponse memory ret) { unchecked {
|
|
287
|
+
uint offset;
|
|
288
|
+
|
|
289
|
+
(ret.version, offset) = response.asUint8CdUnchecked(offset);
|
|
290
|
+
if (ret.version != VERSION)
|
|
291
|
+
revert InvalidResponseVersion();
|
|
292
|
+
|
|
293
|
+
(ret.senderChainId, offset) = response.asUint16CdUnchecked(offset);
|
|
294
|
+
|
|
295
|
+
//for off-chain requests (chainID zero), the requestId is the 65 byte signature
|
|
296
|
+
//for on-chain requests, it is the 32 byte VAA hash
|
|
297
|
+
(ret.requestId, offset) = response.sliceCdUnchecked(offset, ret.senderChainId == 0 ? 65 : 32);
|
|
298
|
+
|
|
299
|
+
uint32 queryReqLen;
|
|
300
|
+
(queryReqLen, offset) = response.asUint32CdUnchecked(offset);
|
|
301
|
+
uint reqOff = offset;
|
|
302
|
+
|
|
303
|
+
{
|
|
304
|
+
uint8 version;
|
|
305
|
+
(version, reqOff) = response.asUint8CdUnchecked(reqOff);
|
|
306
|
+
if (version != ret.version)
|
|
307
|
+
revert VersionMismatch();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
(ret.nonce, reqOff) = response.asUint32CdUnchecked(reqOff);
|
|
311
|
+
|
|
312
|
+
uint8 numPerChainQueries;
|
|
313
|
+
(numPerChainQueries, reqOff) = response.asUint8CdUnchecked(reqOff);
|
|
314
|
+
|
|
315
|
+
//a valid query request must have at least one per-chain-query
|
|
316
|
+
if (numPerChainQueries == 0)
|
|
317
|
+
revert ZeroQueries();
|
|
318
|
+
|
|
319
|
+
//The response starts after the request.
|
|
320
|
+
uint respOff = offset + queryReqLen;
|
|
321
|
+
uint startOfResponse = respOff;
|
|
322
|
+
|
|
323
|
+
uint8 respNumPerChainQueries;
|
|
324
|
+
(respNumPerChainQueries, respOff) = response.asUint8CdUnchecked(respOff);
|
|
325
|
+
if (respNumPerChainQueries != numPerChainQueries)
|
|
326
|
+
revert NumberOfResponsesMismatch();
|
|
327
|
+
|
|
328
|
+
ret.responses = new PerChainQueryResponse[](numPerChainQueries);
|
|
329
|
+
|
|
330
|
+
//walk through the requests and responses in lock step.
|
|
331
|
+
for (uint i; i < numPerChainQueries; ++i) {
|
|
332
|
+
(ret.responses[i].chainId, reqOff) = response.asUint16CdUnchecked(reqOff);
|
|
333
|
+
uint16 respChainId;
|
|
334
|
+
(respChainId, respOff) = response.asUint16CdUnchecked(respOff);
|
|
335
|
+
if (respChainId != ret.responses[i].chainId)
|
|
336
|
+
revert ChainIdMismatch();
|
|
337
|
+
|
|
338
|
+
(ret.responses[i].queryType, reqOff) = response.asUint8CdUnchecked(reqOff);
|
|
339
|
+
QueryType.checkValid(ret.responses[i].queryType);
|
|
340
|
+
uint8 respQueryType;
|
|
341
|
+
(respQueryType, respOff) = response.asUint8CdUnchecked(respOff);
|
|
342
|
+
if (respQueryType != ret.responses[i].queryType)
|
|
343
|
+
revert RequestTypeMismatch();
|
|
344
|
+
|
|
345
|
+
(ret.responses[i].request, reqOff) = response.sliceUint32PrefixedCdUnchecked(reqOff);
|
|
346
|
+
|
|
347
|
+
(ret.responses[i].response, respOff) = response.sliceUint32PrefixedCdUnchecked(respOff);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
//end of request body should align with start of response body
|
|
351
|
+
if (startOfResponse != reqOff)
|
|
352
|
+
revert InvalidPayloadLength(startOfResponse, reqOff);
|
|
353
|
+
|
|
354
|
+
_checkLength(response.length, respOff);
|
|
355
|
+
return ret;
|
|
356
|
+
}}
|
|
357
|
+
|
|
358
|
+
function decodeQueryResponseMem(
|
|
359
|
+
bytes memory response
|
|
360
|
+
) internal pure returns (QueryResponse memory ret) { unchecked {
|
|
361
|
+
uint offset;
|
|
362
|
+
|
|
363
|
+
(ret.version, offset) = response.asUint8MemUnchecked(offset);
|
|
364
|
+
if (ret.version != VERSION)
|
|
365
|
+
revert InvalidResponseVersion();
|
|
366
|
+
|
|
367
|
+
(ret.senderChainId, offset) = response.asUint16MemUnchecked(offset);
|
|
368
|
+
|
|
369
|
+
//for off-chain requests (chainID zero), the requestId is the 65 byte signature
|
|
370
|
+
//for on-chain requests, it is the 32 byte VAA hash
|
|
371
|
+
(ret.requestId, offset) = response.sliceMemUnchecked(offset, ret.senderChainId == 0 ? 65 : 32);
|
|
372
|
+
|
|
373
|
+
uint32 queryReqLen;
|
|
374
|
+
(queryReqLen, offset) = response.asUint32MemUnchecked(offset);
|
|
375
|
+
uint reqOff = offset;
|
|
376
|
+
|
|
377
|
+
{
|
|
378
|
+
uint8 version;
|
|
379
|
+
(version, reqOff) = response.asUint8MemUnchecked(reqOff);
|
|
380
|
+
if (version != ret.version)
|
|
381
|
+
revert VersionMismatch();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
(ret.nonce, reqOff) = response.asUint32MemUnchecked(reqOff);
|
|
385
|
+
|
|
386
|
+
uint8 numPerChainQueries;
|
|
387
|
+
(numPerChainQueries, reqOff) = response.asUint8MemUnchecked(reqOff);
|
|
388
|
+
|
|
389
|
+
//a valid query request must have at least one per-chain-query
|
|
390
|
+
if (numPerChainQueries == 0)
|
|
391
|
+
revert ZeroQueries();
|
|
392
|
+
|
|
393
|
+
//The response starts after the request.
|
|
394
|
+
uint respOff = offset + queryReqLen;
|
|
395
|
+
uint startOfResponse = respOff;
|
|
396
|
+
|
|
397
|
+
uint8 respNumPerChainQueries;
|
|
398
|
+
(respNumPerChainQueries, respOff) = response.asUint8MemUnchecked(respOff);
|
|
399
|
+
if (respNumPerChainQueries != numPerChainQueries)
|
|
400
|
+
revert NumberOfResponsesMismatch();
|
|
401
|
+
|
|
402
|
+
ret.responses = new PerChainQueryResponse[](numPerChainQueries);
|
|
403
|
+
|
|
404
|
+
//walk through the requests and responses in lock step.
|
|
405
|
+
for (uint i; i < numPerChainQueries; ++i) {
|
|
406
|
+
(ret.responses[i].chainId, reqOff) = response.asUint16MemUnchecked(reqOff);
|
|
407
|
+
uint16 respChainId;
|
|
408
|
+
(respChainId, respOff) = response.asUint16MemUnchecked(respOff);
|
|
409
|
+
if (respChainId != ret.responses[i].chainId)
|
|
410
|
+
revert ChainIdMismatch();
|
|
411
|
+
|
|
412
|
+
(ret.responses[i].queryType, reqOff) = response.asUint8MemUnchecked(reqOff);
|
|
413
|
+
QueryType.checkValid(ret.responses[i].queryType);
|
|
414
|
+
uint8 respQueryType;
|
|
415
|
+
(respQueryType, respOff) = response.asUint8MemUnchecked(respOff);
|
|
416
|
+
if (respQueryType != ret.responses[i].queryType)
|
|
417
|
+
revert RequestTypeMismatch();
|
|
418
|
+
|
|
419
|
+
(ret.responses[i].request, reqOff) = response.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
420
|
+
|
|
421
|
+
(ret.responses[i].response, respOff) = response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
//end of request body should align with start of response body
|
|
425
|
+
if (startOfResponse != reqOff)
|
|
426
|
+
revert InvalidPayloadLength(startOfResponse, reqOff);
|
|
427
|
+
|
|
428
|
+
_checkLength(response.length, respOff);
|
|
429
|
+
return ret;
|
|
430
|
+
}}
|
|
431
|
+
|
|
432
|
+
function decodeEthCallQueryResponse(
|
|
433
|
+
PerChainQueryResponse memory pcr
|
|
434
|
+
) internal pure returns (EthCallQueryResponse memory ret) { unchecked {
|
|
435
|
+
if (pcr.queryType != QueryType.ETH_CALL)
|
|
436
|
+
revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL);
|
|
437
|
+
|
|
438
|
+
uint reqOff;
|
|
439
|
+
uint respOff;
|
|
440
|
+
|
|
441
|
+
uint8 numBatchCallData;
|
|
442
|
+
(ret.requestBlockId, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
443
|
+
(numBatchCallData, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
444
|
+
|
|
445
|
+
uint8 respNumResults;
|
|
446
|
+
(ret.blockNum, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
447
|
+
(ret.blockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
448
|
+
(ret.blockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
449
|
+
(respNumResults, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
450
|
+
|
|
451
|
+
if (respNumResults != numBatchCallData)
|
|
452
|
+
revert UnexpectedNumberOfResults();
|
|
453
|
+
|
|
454
|
+
ret.results = new EthCallRecord[](numBatchCallData);
|
|
455
|
+
|
|
456
|
+
//walk through the call inputs and outputs in lock step.
|
|
457
|
+
for (uint i; i < numBatchCallData; ++i) {
|
|
458
|
+
EthCallRecord memory ecr = ret.results[i];
|
|
459
|
+
(ecr.contractAddress, reqOff) = pcr.request.asAddressMemUnchecked(reqOff);
|
|
460
|
+
(ecr.callData, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
461
|
+
|
|
462
|
+
(ecr.result, respOff) = pcr.response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
_checkLength(pcr.request.length, reqOff);
|
|
466
|
+
_checkLength(pcr.response.length, respOff);
|
|
467
|
+
return ret;
|
|
468
|
+
}}
|
|
469
|
+
|
|
470
|
+
function decodeEthCallByTimestampQueryResponse(
|
|
471
|
+
PerChainQueryResponse memory pcr
|
|
472
|
+
) internal pure returns (EthCallByTimestampQueryResponse memory ret) { unchecked {
|
|
473
|
+
if (pcr.queryType != QueryType.ETH_CALL_BY_TIMESTAMP)
|
|
474
|
+
revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL_BY_TIMESTAMP);
|
|
475
|
+
|
|
476
|
+
uint reqOff;
|
|
477
|
+
uint respOff;
|
|
478
|
+
|
|
479
|
+
uint8 numBatchCallData;
|
|
480
|
+
(ret.requestTargetTimestamp, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
481
|
+
(ret.requestTargetBlockIdHint, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
482
|
+
(ret.requestFollowingBlockIdHint, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
483
|
+
(numBatchCallData, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
484
|
+
|
|
485
|
+
uint8 respNumResults;
|
|
486
|
+
(ret.targetBlockNum, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
487
|
+
(ret.targetBlockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
488
|
+
(ret.targetBlockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
489
|
+
(ret.followingBlockNum, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
490
|
+
(ret.followingBlockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
491
|
+
(ret.followingBlockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
492
|
+
(respNumResults, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
493
|
+
|
|
494
|
+
if (respNumResults != numBatchCallData)
|
|
495
|
+
revert UnexpectedNumberOfResults();
|
|
496
|
+
|
|
497
|
+
ret.results = new EthCallRecord[](numBatchCallData);
|
|
498
|
+
|
|
499
|
+
//walk through the call inputs and outputs in lock step.
|
|
500
|
+
for (uint i; i < numBatchCallData; ++i) {
|
|
501
|
+
EthCallRecord memory ecr = ret.results[i];
|
|
502
|
+
(ecr.contractAddress, reqOff) = pcr.request.asAddressMemUnchecked(reqOff);
|
|
503
|
+
(ecr.callData, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
504
|
+
|
|
505
|
+
(ecr.result, respOff) = pcr.response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
_checkLength(pcr.request.length, reqOff);
|
|
509
|
+
_checkLength(pcr.response.length, respOff);
|
|
510
|
+
}}
|
|
511
|
+
|
|
512
|
+
function decodeEthCallWithFinalityQueryResponse(
|
|
513
|
+
PerChainQueryResponse memory pcr
|
|
514
|
+
) internal pure returns (EthCallWithFinalityQueryResponse memory ret) { unchecked {
|
|
515
|
+
if (pcr.queryType != QueryType.ETH_CALL_WITH_FINALITY)
|
|
516
|
+
revert WrongQueryType(pcr.queryType, QueryType.ETH_CALL_WITH_FINALITY);
|
|
517
|
+
|
|
518
|
+
uint reqOff;
|
|
519
|
+
uint respOff;
|
|
520
|
+
|
|
521
|
+
uint8 numBatchCallData;
|
|
522
|
+
(ret.requestBlockId, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
523
|
+
(ret.requestFinality, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
524
|
+
(numBatchCallData, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
525
|
+
|
|
526
|
+
uint8 respNumResults;
|
|
527
|
+
(ret.blockNum, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
528
|
+
(ret.blockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
529
|
+
(ret.blockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
530
|
+
(respNumResults, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
531
|
+
|
|
532
|
+
if (respNumResults != numBatchCallData)
|
|
533
|
+
revert UnexpectedNumberOfResults();
|
|
534
|
+
|
|
535
|
+
ret.results = new EthCallRecord[](numBatchCallData);
|
|
536
|
+
|
|
537
|
+
//walk through the call inputs and outputs in lock step.
|
|
538
|
+
for (uint i; i < numBatchCallData; ++i) {
|
|
539
|
+
EthCallRecord memory ecr = ret.results[i];
|
|
540
|
+
(ecr.contractAddress, reqOff) = pcr.request.asAddressMemUnchecked(reqOff);
|
|
541
|
+
(ecr.callData, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
542
|
+
|
|
543
|
+
(ecr.result, respOff) = pcr.response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
_checkLength(pcr.request.length, reqOff);
|
|
547
|
+
_checkLength(pcr.response.length, respOff);
|
|
548
|
+
}}
|
|
549
|
+
|
|
550
|
+
function decodeSolanaAccountQueryResponse(
|
|
551
|
+
PerChainQueryResponse memory pcr
|
|
552
|
+
) internal pure returns (SolanaAccountQueryResponse memory ret) { unchecked {
|
|
553
|
+
if (pcr.queryType != QueryType.SOLANA_ACCOUNT)
|
|
554
|
+
revert WrongQueryType(pcr.queryType, QueryType.SOLANA_ACCOUNT);
|
|
555
|
+
|
|
556
|
+
uint reqOff;
|
|
557
|
+
uint respOff;
|
|
558
|
+
|
|
559
|
+
uint8 numAccounts;
|
|
560
|
+
(ret.requestCommitment, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
561
|
+
(ret.requestMinContextSlot, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
562
|
+
(ret.requestDataSliceOffset, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
563
|
+
(ret.requestDataSliceLength, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
564
|
+
(numAccounts, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
565
|
+
|
|
566
|
+
uint8 respNumResults;
|
|
567
|
+
(ret.slotNumber, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
568
|
+
(ret.blockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
569
|
+
(ret.blockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
570
|
+
(respNumResults, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
571
|
+
|
|
572
|
+
if (respNumResults != numAccounts)
|
|
573
|
+
revert UnexpectedNumberOfResults();
|
|
574
|
+
|
|
575
|
+
ret.results = new SolanaAccountResult[](numAccounts);
|
|
576
|
+
|
|
577
|
+
//walk through the call inputs and outputs in lock step.
|
|
578
|
+
for (uint i; i < numAccounts; ++i) {
|
|
579
|
+
(ret.results[i].account, reqOff) = pcr.request.asBytes32MemUnchecked(reqOff);
|
|
580
|
+
|
|
581
|
+
(ret.results[i].lamports, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
582
|
+
(ret.results[i].rentEpoch, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
583
|
+
(ret.results[i].executable, respOff) = pcr.response.asBoolMemUnchecked(respOff);
|
|
584
|
+
(ret.results[i].owner, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
585
|
+
(ret.results[i].data, respOff) = pcr.response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_checkLength(pcr.request.length, reqOff);
|
|
589
|
+
_checkLength(pcr.response.length, respOff);
|
|
590
|
+
}}
|
|
591
|
+
|
|
592
|
+
function decodeSolanaPdaQueryResponse(
|
|
593
|
+
PerChainQueryResponse memory pcr
|
|
594
|
+
) internal pure returns (SolanaPdaQueryResponse memory ret) { unchecked {
|
|
595
|
+
if (pcr.queryType != QueryType.SOLANA_PDA)
|
|
596
|
+
revert WrongQueryType(pcr.queryType, QueryType.SOLANA_PDA);
|
|
597
|
+
|
|
598
|
+
uint reqOff;
|
|
599
|
+
uint respOff;
|
|
600
|
+
|
|
601
|
+
(ret.requestCommitment, reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
602
|
+
(ret.requestMinContextSlot, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
603
|
+
(ret.requestDataSliceOffset, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
604
|
+
(ret.requestDataSliceLength, reqOff) = pcr.request.asUint64MemUnchecked(reqOff);
|
|
605
|
+
|
|
606
|
+
uint8 numPdas;
|
|
607
|
+
(numPdas, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
608
|
+
|
|
609
|
+
(ret.slotNumber, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
610
|
+
(ret.blockTime, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
611
|
+
(ret.blockHash, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
612
|
+
|
|
613
|
+
uint8 respNumResults;
|
|
614
|
+
(respNumResults, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
615
|
+
if (respNumResults != numPdas)
|
|
616
|
+
revert UnexpectedNumberOfResults();
|
|
617
|
+
|
|
618
|
+
ret.results = new SolanaPdaResult[](numPdas);
|
|
619
|
+
|
|
620
|
+
//walk through the call inputs and outputs in lock step.
|
|
621
|
+
for (uint i; i < numPdas; ++i) {
|
|
622
|
+
(ret.results[i].programId, reqOff) = pcr.request.asBytes32MemUnchecked(reqOff);
|
|
623
|
+
|
|
624
|
+
uint8 reqNumSeeds;
|
|
625
|
+
(reqNumSeeds, reqOff) = pcr.request.asUint8MemUnchecked(reqOff);
|
|
626
|
+
ret.results[i].seeds = new bytes[](reqNumSeeds);
|
|
627
|
+
for (uint s; s < reqNumSeeds; ++s)
|
|
628
|
+
(ret.results[i].seeds[s], reqOff) = pcr.request.sliceUint32PrefixedMemUnchecked(reqOff);
|
|
629
|
+
|
|
630
|
+
(ret.results[i].account, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
631
|
+
(ret.results[i].bump, respOff) = pcr.response.asUint8MemUnchecked(respOff);
|
|
632
|
+
(ret.results[i].lamports, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
633
|
+
(ret.results[i].rentEpoch, respOff) = pcr.response.asUint64MemUnchecked(respOff);
|
|
634
|
+
(ret.results[i].executable, respOff) = pcr.response.asBoolMemUnchecked(respOff);
|
|
635
|
+
(ret.results[i].owner, respOff) = pcr.response.asBytes32MemUnchecked(respOff);
|
|
636
|
+
(ret.results[i].data, respOff) = pcr.response.sliceUint32PrefixedMemUnchecked(respOff);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
_checkLength(pcr.request.length, reqOff);
|
|
640
|
+
_checkLength(pcr.response.length, respOff);
|
|
641
|
+
}}
|
|
642
|
+
|
|
643
|
+
function validateBlockTime(
|
|
644
|
+
uint64 blockTimeInMicroSeconds,
|
|
645
|
+
uint256 minBlockTimeInSeconds
|
|
646
|
+
) internal pure {
|
|
647
|
+
uint256 blockTimeInSeconds = blockTimeInMicroSeconds / MICROSECONDS_PER_SECOND; // Rounds down
|
|
648
|
+
|
|
649
|
+
if (blockTimeInSeconds < minBlockTimeInSeconds)
|
|
650
|
+
revert StaleBlockTime();
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function validateBlockNum(uint64 blockNum, uint256 minBlockNum) internal pure {
|
|
654
|
+
if (blockNum < minBlockNum)
|
|
655
|
+
revert StaleBlockNum();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function validateChainId(
|
|
659
|
+
uint16 chainId,
|
|
660
|
+
uint16[] memory validChainIds
|
|
661
|
+
) internal pure { unchecked {
|
|
662
|
+
uint len = validChainIds.length;
|
|
663
|
+
for (uint i; i < len; ++i)
|
|
664
|
+
if (chainId == validChainIds[i])
|
|
665
|
+
return;
|
|
666
|
+
|
|
667
|
+
revert InvalidChainId();
|
|
668
|
+
}}
|
|
669
|
+
|
|
670
|
+
function validateEthCallRecord(
|
|
671
|
+
EthCallRecord[] memory ecrs,
|
|
672
|
+
address[] memory validContractAddresses,
|
|
673
|
+
bytes4[] memory validFunctionSignatures
|
|
674
|
+
) internal pure { unchecked {
|
|
675
|
+
uint len = ecrs.length;
|
|
676
|
+
for (uint i; i < len; ++i)
|
|
677
|
+
validateEthCallRecord(ecrs[i], validContractAddresses, validFunctionSignatures);
|
|
678
|
+
}}
|
|
679
|
+
|
|
680
|
+
//validates that EthCallRecord a valid function signature and contract address
|
|
681
|
+
//An empty array means we accept all addresses/function signatures
|
|
682
|
+
// Example 1: To accept signatures 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)`
|
|
683
|
+
// you'd pass in [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd)]
|
|
684
|
+
// Example 2: To accept any function signatures from `address(abcd)` or `address(efab)`
|
|
685
|
+
// you'd pass in [], [address(abcd), address(efab)]
|
|
686
|
+
// Example 3: To accept function signature 0xaaaaaaaa from any address
|
|
687
|
+
// you'd pass in [0xaaaaaaaa], []
|
|
688
|
+
//
|
|
689
|
+
// WARNING Example 4: If you want to accept signature 0xaaaaaaaa from `address(abcd)`
|
|
690
|
+
// and signature 0xbbbbbbbb from `address(efab)` the following input would be incorrect:
|
|
691
|
+
// [0xaaaaaaaa, 0xbbbbbbbb], [address(abcd), address(efab)]
|
|
692
|
+
// This would accept both 0xaaaaaaaa and 0xbbbbbbbb from `address(abcd)` AND `address(efab)`.
|
|
693
|
+
// Instead you should make 2 calls to this method using the pattern in Example 1.
|
|
694
|
+
// [0xaaaaaaaa], [address(abcd)] OR [0xbbbbbbbb], [address(efab)]
|
|
695
|
+
function validateEthCallRecord(
|
|
696
|
+
EthCallRecord memory ecd,
|
|
697
|
+
address[] memory validContractAddresses, //empty array means accept all
|
|
698
|
+
bytes4[] memory validFunctionSignatures //empty array means accept all
|
|
699
|
+
) internal pure {
|
|
700
|
+
if (validContractAddresses.length > 0)
|
|
701
|
+
_validateContractAddress(ecd.contractAddress, validContractAddresses);
|
|
702
|
+
|
|
703
|
+
if (validFunctionSignatures.length > 0) {
|
|
704
|
+
if (ecd.callData.length < 4)
|
|
705
|
+
revert InvalidFunctionSignature();
|
|
706
|
+
|
|
707
|
+
(bytes4 funcSig, ) = ecd.callData.asBytes4MemUnchecked(0);
|
|
708
|
+
_validateFunctionSignature(funcSig, validFunctionSignatures);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function _validateContractAddress(
|
|
713
|
+
address contractAddress,
|
|
714
|
+
address[] memory validContractAddresses
|
|
715
|
+
) private pure { unchecked {
|
|
716
|
+
uint len = validContractAddresses.length;
|
|
717
|
+
for (uint i; i < len; ++i)
|
|
718
|
+
if (contractAddress == validContractAddresses[i])
|
|
719
|
+
return;
|
|
720
|
+
|
|
721
|
+
revert InvalidContractAddress();
|
|
722
|
+
}}
|
|
723
|
+
|
|
724
|
+
function _validateFunctionSignature(
|
|
725
|
+
bytes4 functionSignature,
|
|
726
|
+
bytes4[] memory validFunctionSignatures
|
|
727
|
+
) private pure { unchecked {
|
|
728
|
+
uint len = validFunctionSignatures.length;
|
|
729
|
+
for (uint i; i < len; ++i)
|
|
730
|
+
if (functionSignature == validFunctionSignatures[i])
|
|
731
|
+
return;
|
|
732
|
+
|
|
733
|
+
revert InvalidFunctionSignature();
|
|
734
|
+
}}
|
|
735
|
+
|
|
736
|
+
//we use this over BytesParsing.checkLength to return our custom errors in all error cases
|
|
737
|
+
function _checkLength(uint256 length, uint256 expected) private pure {
|
|
738
|
+
if (length != expected)
|
|
739
|
+
revert InvalidPayloadLength(length, expected);
|
|
740
|
+
}
|
|
741
|
+
}
|