@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.
Files changed (56) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +78 -0
  3. package/contracts/Executor/Integration.sol +180 -0
  4. package/contracts/Executor/RelayInstruction.sol +108 -0
  5. package/contracts/Executor/Request.sol +43 -0
  6. package/contracts/RawDispatcher.sol +29 -0
  7. package/contracts/Utils.sol +27 -0
  8. package/contracts/WormholeRelayer/AdditionalMessages.sol +37 -0
  9. package/contracts/WormholeRelayer/Keys.sol +198 -0
  10. package/contracts/WormholeRelayer/Receiver.sol +44 -0
  11. package/contracts/WormholeRelayer/Sender.sol +264 -0
  12. package/contracts/WormholeRelayer.sol +7 -0
  13. package/contracts/constants/CctpDomainMapping.sol +72 -0
  14. package/contracts/constants/CctpDomains.sol +21 -0
  15. package/contracts/constants/Chains.sol +66 -0
  16. package/contracts/constants/Common.sol +17 -0
  17. package/contracts/constants/ConsistencyLevel.sol +9 -0
  18. package/contracts/interfaces/ICoreBridge.sol +80 -0
  19. package/contracts/interfaces/ICustomConsistencyLevel.sol +12 -0
  20. package/contracts/interfaces/IDeliveryProvider.sol +84 -0
  21. package/contracts/interfaces/IExecutor.sol +32 -0
  22. package/contracts/interfaces/ITokenBridge.sol +81 -0
  23. package/contracts/interfaces/IWormholeRelayer.sol +658 -0
  24. package/contracts/interfaces/cctp/IMessageTransmitter.sol +76 -0
  25. package/contracts/interfaces/cctp/ITokenMessenger.sol +72 -0
  26. package/contracts/interfaces/cctp/ITokenMinter.sol +40 -0
  27. package/contracts/interfaces/cctp/shared/IOwnable2Step.sol +18 -0
  28. package/contracts/interfaces/cctp/shared/IPausable.sol +20 -0
  29. package/contracts/interfaces/token/IERC20.sol +16 -0
  30. package/contracts/interfaces/token/IERC20Metadata.sol +11 -0
  31. package/contracts/interfaces/token/IERC20Permit.sol +19 -0
  32. package/contracts/interfaces/token/IWETH.sol +12 -0
  33. package/contracts/libraries/BytesParsing.sol +2848 -0
  34. package/contracts/libraries/CctpMessages.sol +755 -0
  35. package/contracts/libraries/CoreBridge.sol +365 -0
  36. package/contracts/libraries/CustomConsistency.sol +60 -0
  37. package/contracts/libraries/Percentage.sol +99 -0
  38. package/contracts/libraries/PermitParsing.sol +580 -0
  39. package/contracts/libraries/QueryResponse.sol +741 -0
  40. package/contracts/libraries/ReplayProtection.sol +106 -0
  41. package/contracts/libraries/SafeERC20.sol +67 -0
  42. package/contracts/libraries/TokenBridgeMessages.sol +742 -0
  43. package/contracts/libraries/TypedUnits.sol +304 -0
  44. package/contracts/libraries/UncheckedIndexing.sol +99 -0
  45. package/contracts/libraries/VaaLib.sol +1385 -0
  46. package/contracts/proxy/Eip1967Admin.sol +20 -0
  47. package/contracts/proxy/Eip1967Implementation.sol +15 -0
  48. package/contracts/proxy/Proxy.sol +43 -0
  49. package/contracts/proxy/ProxyBase.sol +80 -0
  50. package/contracts/utils/DecimalNormalization.sol +18 -0
  51. package/contracts/utils/EagerOps.sol +18 -0
  52. package/contracts/utils/Keccak.sol +39 -0
  53. package/contracts/utils/Revert.sol +11 -0
  54. package/contracts/utils/Transfer.sol +23 -0
  55. package/contracts/utils/UniversalAddress.sol +17 -0
  56. 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
+ }