-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
VRFV2PlusWrapper.sol
716 lines (639 loc) · 29.2 KB
/
VRFV2PlusWrapper.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
import {VRFConsumerBaseV2Plus} from "./VRFConsumerBaseV2Plus.sol";
import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol";
import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol";
import {VRFV2PlusClient} from "./libraries/VRFV2PlusClient.sol";
import {IVRFV2PlusWrapper} from "./interfaces/IVRFV2PlusWrapper.sol";
import {VRFV2PlusWrapperConsumerBase} from "./VRFV2PlusWrapperConsumerBase.sol";
import {ChainSpecificUtil} from "../../ChainSpecificUtil.sol";
/**
* @notice A wrapper for VRFCoordinatorV2 that provides an interface better suited to one-off
* @notice requests for randomness.
*/
// solhint-disable-next-line max-states-count
contract VRFV2PlusWrapper is ConfirmedOwner, TypeAndVersionInterface, VRFConsumerBaseV2Plus, IVRFV2PlusWrapper {
event WrapperFulfillmentFailed(uint256 indexed requestId, address indexed consumer);
// upper bound limit for premium percentages to make sure fee calculations don't overflow
uint8 private constant PREMIUM_PERCENTAGE_MAX = 155;
// 5k is plenty for an EXTCODESIZE call (2600) + warm CALL (100)
// and some arithmetic operations.
uint256 private constant GAS_FOR_CALL_EXACT_CHECK = 5_000;
uint16 private constant EXPECTED_MIN_LENGTH = 36;
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
uint256 public immutable SUBSCRIPTION_ID;
LinkTokenInterface internal immutable i_link;
AggregatorV3Interface internal immutable i_link_native_feed;
event FulfillmentTxSizeSet(uint32 size);
event ConfigSet(
uint32 wrapperGasOverhead,
uint32 coordinatorGasOverheadNative,
uint32 coordinatorGasOverheadLink,
uint16 coordinatorGasOverheadPerWord,
uint8 coordinatorNativePremiumPercentage,
uint8 coordinatorLinkPremiumPercentage,
bytes32 keyHash,
uint8 maxNumWords,
uint32 stalenessSeconds,
int256 fallbackWeiPerUnitLink,
uint32 fulfillmentFlatFeeNativePPM,
uint32 fulfillmentFlatFeeLinkDiscountPPM
);
event FallbackWeiPerUnitLinkUsed(uint256 requestId, int256 fallbackWeiPerUnitLink);
event Withdrawn(address indexed to, uint256 amount);
event NativeWithdrawn(address indexed to, uint256 amount);
event Enabled();
event Disabled();
error LinkAlreadySet();
error LinkDiscountTooHigh(uint32 flatFeeLinkDiscountPPM, uint32 flatFeeNativePPM);
error InvalidPremiumPercentage(uint8 premiumPercentage, uint8 max);
error FailedToTransferLink();
error IncorrectExtraArgsLength(uint16 expectedMinimumLength, uint16 actualLength);
error NativePaymentInOnTokenTransfer();
error LINKPaymentInRequestRandomWordsInNative();
error SubscriptionIdMissing();
/* Storage Slot 1: BEGIN */
// 20 bytes used by VRFConsumerBaseV2Plus.s_vrfCoordinator
// s_configured tracks whether this contract has been configured. If not configured, randomness
// requests cannot be made.
bool public s_configured;
// s_disabled disables the contract when true. When disabled, new VRF requests cannot be made
// but existing ones can still be fulfilled.
bool public s_disabled;
// s_maxNumWords is the max number of words that can be requested in a single wrapped VRF request.
uint8 internal s_maxNumWords;
// 9 bytes left
/* Storage Slot 1: END */
/* Storage Slot 2: BEGIN */
// s_keyHash is the key hash to use when requesting randomness. Fees are paid based on current gas
// fees, so this should be set to the highest gas lane on the network.
bytes32 internal s_keyHash;
/* Storage Slot 2: END */
/* Storage Slot 3: BEGIN */
// lastRequestId is the request ID of the most recent VRF V2 request made by this wrapper. This
// should only be relied on within the same transaction the request was made.
uint256 public override lastRequestId;
/* Storage Slot 3: END */
/* Storage Slot 4: BEGIN */
// s_fallbackWeiPerUnitLink is the backup LINK exchange rate used when the LINK/NATIVE feed is
// stale.
int256 private s_fallbackWeiPerUnitLink;
/* Storage Slot 4: END */
/* Storage Slot 5: BEGIN */
// s_stalenessSeconds is the number of seconds before we consider the feed price to be stale and
// fallback to fallbackWeiPerUnitLink.
uint32 private s_stalenessSeconds;
// s_wrapperGasOverhead reflects the gas overhead of the wrapper's fulfillRandomWords
// function. The cost for this gas is passed to the user.
uint32 private s_wrapperGasOverhead;
// Configuration fetched from VRFCoordinatorV2
/// @dev this is the size of a VRF v2 fulfillment's calldata abi-encoded in bytes.
/// @dev proofSize = 13 words = 13 * 256 = 3328 bits
/// @dev commitmentSize = 5 words = 5 * 256 = 1280 bits
/// @dev dataSize = proofSize + commitmentSize = 4608 bits
/// @dev selector = 32 bits
/// @dev total data size = 4608 bits + 32 bits = 4640 bits = 580 bytes
uint32 public s_fulfillmentTxSizeBytes = 580;
// s_coordinatorGasOverheadNative reflects the gas overhead of the coordinator's fulfillRandomWords
// function for native payment. The cost for this gas is billed to the subscription, and must therefor be included
// in the pricing for wrapped requests. This includes the gas costs of proof verification and
// payment calculation in the coordinator.
uint32 private s_coordinatorGasOverheadNative;
// s_coordinatorGasOverheadLink reflects the gas overhead of the coordinator's fulfillRandomWords
// function for link payment. The cost for this gas is billed to the subscription, and must therefor be included
// in the pricing for wrapped requests. This includes the gas costs of proof verification and
// payment calculation in the coordinator.
uint32 private s_coordinatorGasOverheadLink;
uint16 private s_coordinatorGasOverheadPerWord;
// s_fulfillmentFlatFeeLinkPPM is the flat fee in millionths of native that VRFCoordinatorV2
// charges for native payment.
uint32 private s_fulfillmentFlatFeeNativePPM;
// s_fulfillmentFlatFeeLinkDiscountPPM is the flat fee discount in millionths of native that VRFCoordinatorV2
// charges for link payment.
uint32 private s_fulfillmentFlatFeeLinkDiscountPPM;
// s_coordinatorNativePremiumPercentage is the coordinator's premium ratio in percentage for native payment.
// For example, a value of 0 indicates no premium. A value of 15 indicates a 15 percent premium.
// Wrapper has no premium. This premium is for VRFCoordinator.
uint8 private s_coordinatorNativePremiumPercentage;
// s_coordinatorLinkPremiumPercentage is the premium ratio in percentage for link payment. For example, a
// value of 0 indicates no premium. A value of 15 indicates a 15 percent premium.
// Wrapper has no premium. This premium is for VRFCoordinator.
uint8 private s_coordinatorLinkPremiumPercentage;
/* Storage Slot 5: END */
struct Callback {
address callbackAddress;
uint32 callbackGasLimit;
// Reducing requestGasPrice from uint256 to uint64 slots Callback struct
// into a single word, thus saving an entire SSTORE and leading to 21K
// gas cost saving. 18 ETH would be the max gas price we can process.
// GasPrice is unlikely to be more than 14 ETH on most chains
uint64 requestGasPrice;
}
/* Storage Slot 6: BEGIN */
mapping(uint256 => Callback) /* requestID */ /* callback */ public s_callbacks;
/* Storage Slot 6: END */
constructor(
address _link,
address _linkNativeFeed,
address _coordinator,
uint256 _subId
) VRFConsumerBaseV2Plus(_coordinator) {
i_link = LinkTokenInterface(_link);
i_link_native_feed = AggregatorV3Interface(_linkNativeFeed);
if (_subId == 0) {
revert SubscriptionIdMissing();
}
// Sanity check: should revert if the subscription does not exist
s_vrfCoordinator.getSubscription(_subId);
// Subscription for the wrapper is created and managed by an external account.
// Expectation is that wrapper contract address will be added as a consumer
// to this subscription by the external account (owner of the subscription).
// Migration of the wrapper's subscription to the new coordinator has to be
// handled by the external account (owner of the subscription).
SUBSCRIPTION_ID = _subId;
}
/**
* @notice setFulfillmentTxSize sets the size of the fulfillment transaction in bytes.
* @param size is the size of the fulfillment transaction in bytes.
*/
function setFulfillmentTxSize(uint32 size) external onlyOwner {
s_fulfillmentTxSizeBytes = size;
emit FulfillmentTxSizeSet(size);
}
/**
* @notice setConfig configures VRFV2Wrapper.
*
* @dev Sets wrapper-specific configuration based on the given parameters, and fetches any needed
* @dev VRFCoordinatorV2 configuration from the coordinator.
*
* @param _wrapperGasOverhead reflects the gas overhead of the wrapper's fulfillRandomWords
* function.
*
* @param _coordinatorGasOverheadNative reflects the gas overhead of the coordinator's
* fulfillRandomWords function for native payment.
*
* @param _coordinatorGasOverheadLink reflects the gas overhead of the coordinator's
* fulfillRandomWords function for link payment.
*
* @param _coordinatorGasOverheadPerWord reflects the gas overhead per word of the coordinator's
* fulfillRandomWords function.
*
* @param _coordinatorNativePremiumPercentage is the coordinator's premium ratio in percentage for requests paid in native.
*
* @param _coordinatorLinkPremiumPercentage is the coordinator's premium ratio in percentage for requests paid in link.
*
* @param _keyHash to use for requesting randomness.
* @param _maxNumWords is the max number of words that can be requested in a single wrapped VRF request
* @param _stalenessSeconds is the number of seconds before we consider the feed price to be stale
* and fallback to fallbackWeiPerUnitLink.
*
* @param _fallbackWeiPerUnitLink is the backup LINK exchange rate used when the LINK/NATIVE feed
* is stale.
*
* @param _fulfillmentFlatFeeNativePPM is the flat fee in millionths of native that VRFCoordinatorV2Plus
* charges for native payment.
*
* @param _fulfillmentFlatFeeLinkDiscountPPM is the flat fee discount in millionths of native that VRFCoordinatorV2Plus
* charges for link payment.
*/
function setConfig(
uint32 _wrapperGasOverhead,
uint32 _coordinatorGasOverheadNative,
uint32 _coordinatorGasOverheadLink,
uint16 _coordinatorGasOverheadPerWord,
uint8 _coordinatorNativePremiumPercentage,
uint8 _coordinatorLinkPremiumPercentage,
bytes32 _keyHash,
uint8 _maxNumWords,
uint32 _stalenessSeconds,
int256 _fallbackWeiPerUnitLink,
uint32 _fulfillmentFlatFeeNativePPM,
uint32 _fulfillmentFlatFeeLinkDiscountPPM
) external onlyOwner {
if (_fulfillmentFlatFeeLinkDiscountPPM > _fulfillmentFlatFeeNativePPM) {
revert LinkDiscountTooHigh(_fulfillmentFlatFeeLinkDiscountPPM, _fulfillmentFlatFeeNativePPM);
}
if (_coordinatorNativePremiumPercentage > PREMIUM_PERCENTAGE_MAX) {
revert InvalidPremiumPercentage(_coordinatorNativePremiumPercentage, PREMIUM_PERCENTAGE_MAX);
}
if (_coordinatorLinkPremiumPercentage > PREMIUM_PERCENTAGE_MAX) {
revert InvalidPremiumPercentage(_coordinatorLinkPremiumPercentage, PREMIUM_PERCENTAGE_MAX);
}
s_wrapperGasOverhead = _wrapperGasOverhead;
s_coordinatorGasOverheadNative = _coordinatorGasOverheadNative;
s_coordinatorGasOverheadLink = _coordinatorGasOverheadLink;
s_coordinatorGasOverheadPerWord = _coordinatorGasOverheadPerWord;
s_coordinatorNativePremiumPercentage = _coordinatorNativePremiumPercentage;
s_coordinatorLinkPremiumPercentage = _coordinatorLinkPremiumPercentage;
s_keyHash = _keyHash;
s_maxNumWords = _maxNumWords;
s_configured = true;
// Get other configuration from coordinator
s_stalenessSeconds = _stalenessSeconds;
s_fallbackWeiPerUnitLink = _fallbackWeiPerUnitLink;
s_fulfillmentFlatFeeNativePPM = _fulfillmentFlatFeeNativePPM;
s_fulfillmentFlatFeeLinkDiscountPPM = _fulfillmentFlatFeeLinkDiscountPPM;
emit ConfigSet(
_wrapperGasOverhead,
_coordinatorGasOverheadNative,
_coordinatorGasOverheadLink,
_coordinatorGasOverheadPerWord,
_coordinatorNativePremiumPercentage,
_coordinatorLinkPremiumPercentage,
_keyHash,
_maxNumWords,
_stalenessSeconds,
_fallbackWeiPerUnitLink,
_fulfillmentFlatFeeNativePPM,
s_fulfillmentFlatFeeLinkDiscountPPM
);
}
/**
* @notice getConfig returns the current VRFV2Wrapper configuration.
*
* @return fallbackWeiPerUnitLink is the backup LINK exchange rate used when the LINK/NATIVE feed
* is stale.
*
* @return stalenessSeconds is the number of seconds before we consider the feed price to be stale
* and fallback to fallbackWeiPerUnitLink.
*
* @return fulfillmentFlatFeeNativePPM is the flat fee in millionths of native that VRFCoordinatorV2Plus
* charges for native payment.
*
* @return fulfillmentFlatFeeLinkDiscountPPM is the flat fee discount in millionths of native that VRFCoordinatorV2Plus
* charges for link payment.
*
* @return wrapperGasOverhead reflects the gas overhead of the wrapper's fulfillRandomWords
* function. The cost for this gas is passed to the user.
*
* @return coordinatorGasOverheadNative reflects the gas overhead of the coordinator's
* fulfillRandomWords function for native payment.
*
* @return coordinatorGasOverheadLink reflects the gas overhead of the coordinator's
* fulfillRandomWords function for link payment.
*
* @return coordinatorGasOverheadPerWord reflects the gas overhead per word of the coordinator's
* fulfillRandomWords function.
*
* @return wrapperNativePremiumPercentage is the premium ratio in percentage for native payment. For example, a value of 0
* indicates no premium. A value of 15 indicates a 15 percent premium.
*
* @return wrapperLinkPremiumPercentage is the premium ratio in percentage for link payment. For example, a value of 0
* indicates no premium. A value of 15 indicates a 15 percent premium.
*
* @return keyHash is the key hash to use when requesting randomness. Fees are paid based on
* current gas fees, so this should be set to the highest gas lane on the network.
*
* @return maxNumWords is the max number of words that can be requested in a single wrapped VRF
* request.
*/
function getConfig()
external
view
returns (
int256 fallbackWeiPerUnitLink,
uint32 stalenessSeconds,
uint32 fulfillmentFlatFeeNativePPM,
uint32 fulfillmentFlatFeeLinkDiscountPPM,
uint32 wrapperGasOverhead,
uint32 coordinatorGasOverheadNative,
uint32 coordinatorGasOverheadLink,
uint16 coordinatorGasOverheadPerWord,
uint8 wrapperNativePremiumPercentage,
uint8 wrapperLinkPremiumPercentage,
bytes32 keyHash,
uint8 maxNumWords
)
{
return (
s_fallbackWeiPerUnitLink,
s_stalenessSeconds,
s_fulfillmentFlatFeeNativePPM,
s_fulfillmentFlatFeeLinkDiscountPPM,
s_wrapperGasOverhead,
s_coordinatorGasOverheadNative,
s_coordinatorGasOverheadLink,
s_coordinatorGasOverheadPerWord,
s_coordinatorNativePremiumPercentage,
s_coordinatorLinkPremiumPercentage,
s_keyHash,
s_maxNumWords
);
}
/**
* @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
* @notice block.
*
* @dev This function relies on the transaction gas price which is not automatically set during
* @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
*
* @param _callbackGasLimit is the gas limit used to estimate the price.
*/
function calculateRequestPrice(
uint32 _callbackGasLimit,
uint32 _numWords
) external view override onlyConfiguredNotDisabled returns (uint256) {
(int256 weiPerUnitLink, ) = _getFeedData();
return _calculateRequestPrice(_callbackGasLimit, _numWords, tx.gasprice, weiPerUnitLink);
}
function calculateRequestPriceNative(
uint32 _callbackGasLimit,
uint32 _numWords
) external view override onlyConfiguredNotDisabled returns (uint256) {
return _calculateRequestPriceNative(_callbackGasLimit, _numWords, tx.gasprice);
}
/**
* @notice Estimates the price of a VRF request with a specific gas limit and gas price.
*
* @dev This is a convenience function that can be called in simulation to better understand
* @dev pricing.
*
* @param _callbackGasLimit is the gas limit used to estimate the price.
* @param _requestGasPriceWei is the gas price in wei used for the estimation.
*/
function estimateRequestPrice(
uint32 _callbackGasLimit,
uint32 _numWords,
uint256 _requestGasPriceWei
) external view override onlyConfiguredNotDisabled returns (uint256) {
(int256 weiPerUnitLink, ) = _getFeedData();
return _calculateRequestPrice(_callbackGasLimit, _numWords, _requestGasPriceWei, weiPerUnitLink);
}
function estimateRequestPriceNative(
uint32 _callbackGasLimit,
uint32 _numWords,
uint256 _requestGasPriceWei
) external view override onlyConfiguredNotDisabled returns (uint256) {
return _calculateRequestPriceNative(_callbackGasLimit, _numWords, _requestGasPriceWei);
}
function _calculateRequestPriceNative(
uint256 _gas,
uint32 _numWords,
uint256 _requestGasPrice
) internal view returns (uint256) {
// costWei is the base fee denominated in wei (native)
// (wei/gas) * gas
uint256 wrapperCostWei = _requestGasPrice * s_wrapperGasOverhead;
// coordinatorCostWei takes into account the L1 posting costs of the VRF fulfillment transaction, if we are on an L2.
// (wei/gas) * gas + l1wei
uint256 coordinatorCostWei = _requestGasPrice *
(_gas + _getCoordinatorGasOverhead(_numWords, true)) +
ChainSpecificUtil._getL1CalldataGasCost(s_fulfillmentTxSizeBytes);
// coordinatorCostWithPremiumAndFlatFeeWei is the coordinator cost with the percentage premium and flat fee applied
// coordinator cost * premium multiplier + flat fee
uint256 coordinatorCostWithPremiumAndFlatFeeWei = ((coordinatorCostWei *
(s_coordinatorNativePremiumPercentage + 100)) / 100) + (1e12 * uint256(s_fulfillmentFlatFeeNativePPM));
return wrapperCostWei + coordinatorCostWithPremiumAndFlatFeeWei;
}
function _calculateRequestPrice(
uint256 _gas,
uint32 _numWords,
uint256 _requestGasPrice,
int256 _weiPerUnitLink
) internal view returns (uint256) {
// costWei is the base fee denominated in wei (native)
// (wei/gas) * gas
uint256 wrapperCostWei = _requestGasPrice * s_wrapperGasOverhead;
// coordinatorCostWei takes into account the L1 posting costs of the VRF fulfillment transaction, if we are on an L2.
// (wei/gas) * gas + l1wei
uint256 coordinatorCostWei = _requestGasPrice *
(_gas + _getCoordinatorGasOverhead(_numWords, false)) +
ChainSpecificUtil._getL1CalldataGasCost(s_fulfillmentTxSizeBytes);
// coordinatorCostWithPremiumAndFlatFeeWei is the coordinator cost with the percentage premium and flat fee applied
// coordinator cost * premium multiplier + flat fee
uint256 coordinatorCostWithPremiumAndFlatFeeWei = ((coordinatorCostWei *
(s_coordinatorLinkPremiumPercentage + 100)) / 100) +
(1e12 * uint256(s_fulfillmentFlatFeeNativePPM - s_fulfillmentFlatFeeLinkDiscountPPM));
// requestPrice is denominated in juels (link)
// (1e18 juels/link) * wei / (wei/link) = juels
return (1e18 * (wrapperCostWei + coordinatorCostWithPremiumAndFlatFeeWei)) / uint256(_weiPerUnitLink);
}
/**
* @notice onTokenTransfer is called by LinkToken upon payment for a VRF request.
*
* @dev Reverts if payment is too low.
*
* @param _sender is the sender of the payment, and the address that will receive a VRF callback
* upon fulfillment.
*
* @param _amount is the amount of LINK paid in Juels.
*
* @param _data is the abi-encoded VRF request parameters: uint32 callbackGasLimit,
* uint16 requestConfirmations, and uint32 numWords.
*/
function onTokenTransfer(address _sender, uint256 _amount, bytes calldata _data) external onlyConfiguredNotDisabled {
// solhint-disable-next-line gas-custom-errors
require(msg.sender == address(i_link), "only callable from LINK");
(uint32 callbackGasLimit, uint16 requestConfirmations, uint32 numWords, bytes memory extraArgs) = abi.decode(
_data,
(uint32, uint16, uint32, bytes)
);
checkPaymentMode(extraArgs, true);
uint32 eip150Overhead = _getEIP150Overhead(callbackGasLimit);
(int256 weiPerUnitLink, bool isFeedStale) = _getFeedData();
uint256 price = _calculateRequestPrice(callbackGasLimit, numWords, tx.gasprice, weiPerUnitLink);
// solhint-disable-next-line gas-custom-errors
require(_amount >= price, "fee too low");
// solhint-disable-next-line gas-custom-errors
require(numWords <= s_maxNumWords, "numWords too high");
VRFV2PlusClient.RandomWordsRequest memory req = VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: SUBSCRIPTION_ID,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit + eip150Overhead + s_wrapperGasOverhead,
numWords: numWords,
extraArgs: extraArgs // empty extraArgs defaults to link payment
});
uint256 requestId = s_vrfCoordinator.requestRandomWords(req);
s_callbacks[requestId] = Callback({
callbackAddress: _sender,
callbackGasLimit: callbackGasLimit,
requestGasPrice: uint64(tx.gasprice)
});
lastRequestId = requestId;
if (isFeedStale) {
emit FallbackWeiPerUnitLinkUsed(requestId, s_fallbackWeiPerUnitLink);
}
}
function checkPaymentMode(bytes memory extraArgs, bool isLinkMode) public pure {
// If extraArgs is empty, payment mode is LINK by default
if (extraArgs.length == 0) {
if (!isLinkMode) {
revert LINKPaymentInRequestRandomWordsInNative();
}
return;
}
if (extraArgs.length < EXPECTED_MIN_LENGTH) {
revert IncorrectExtraArgsLength(EXPECTED_MIN_LENGTH, uint16(extraArgs.length));
}
// ExtraArgsV1 only has struct {bool nativePayment} as of now
// The following condition checks if nativePayment in abi.encode of
// ExtraArgsV1 matches the appropriate function call (onTokenTransfer
// for LINK and requestRandomWordsInNative for Native payment)
bool nativePayment = extraArgs[35] == hex"01";
if (nativePayment && isLinkMode) {
revert NativePaymentInOnTokenTransfer();
}
if (!nativePayment && !isLinkMode) {
revert LINKPaymentInRequestRandomWordsInNative();
}
}
function requestRandomWordsInNative(
uint32 _callbackGasLimit,
uint16 _requestConfirmations,
uint32 _numWords,
bytes calldata extraArgs
) external payable override onlyConfiguredNotDisabled returns (uint256 requestId) {
checkPaymentMode(extraArgs, false);
uint32 eip150Overhead = _getEIP150Overhead(_callbackGasLimit);
uint256 price = _calculateRequestPriceNative(_callbackGasLimit, _numWords, tx.gasprice);
// solhint-disable-next-line gas-custom-errors
require(msg.value >= price, "fee too low");
// solhint-disable-next-line gas-custom-errors
require(_numWords <= s_maxNumWords, "numWords too high");
VRFV2PlusClient.RandomWordsRequest memory req = VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: SUBSCRIPTION_ID,
requestConfirmations: _requestConfirmations,
callbackGasLimit: _callbackGasLimit + eip150Overhead + s_wrapperGasOverhead,
numWords: _numWords,
extraArgs: extraArgs
});
requestId = s_vrfCoordinator.requestRandomWords(req);
s_callbacks[requestId] = Callback({
callbackAddress: msg.sender,
callbackGasLimit: _callbackGasLimit,
requestGasPrice: uint64(tx.gasprice)
});
return requestId;
}
/**
* @notice withdraw is used by the VRFV2Wrapper's owner to withdraw LINK revenue.
*
* @param _recipient is the address that should receive the LINK funds.
*/
function withdraw(address _recipient) external onlyOwner {
uint256 amount = i_link.balanceOf(address(this));
if (!i_link.transfer(_recipient, amount)) {
revert FailedToTransferLink();
}
emit Withdrawn(_recipient, amount);
}
/**
* @notice withdraw is used by the VRFV2Wrapper's owner to withdraw native revenue.
*
* @param _recipient is the address that should receive the native funds.
*/
function withdrawNative(address _recipient) external onlyOwner {
uint256 amount = address(this).balance;
(bool success, ) = payable(_recipient).call{value: amount}("");
// solhint-disable-next-line gas-custom-errors
require(success, "failed to withdraw native");
emit NativeWithdrawn(_recipient, amount);
}
/**
* @notice enable this contract so that new requests can be accepted.
*/
function enable() external onlyOwner {
s_disabled = false;
emit Enabled();
}
/**
* @notice disable this contract so that new requests will be rejected. When disabled, new requests
* @notice will revert but existing requests can still be fulfilled.
*/
function disable() external onlyOwner {
s_disabled = true;
emit Disabled();
}
// solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
function fulfillRandomWords(uint256 _requestId, uint256[] calldata _randomWords) internal override {
Callback memory callback = s_callbacks[_requestId];
delete s_callbacks[_requestId];
address callbackAddress = callback.callbackAddress;
// solhint-disable-next-line gas-custom-errors
require(callbackAddress != address(0), "request not found"); // This should never happen
VRFV2PlusWrapperConsumerBase c;
bytes memory resp = abi.encodeWithSelector(c.rawFulfillRandomWords.selector, _requestId, _randomWords);
bool success = _callWithExactGas(callback.callbackGasLimit, callbackAddress, resp);
if (!success) {
emit WrapperFulfillmentFailed(_requestId, callbackAddress);
}
}
function link() external view override returns (address) {
return address(i_link);
}
function linkNativeFeed() external view override returns (address) {
return address(i_link_native_feed);
}
function _getFeedData() private view returns (int256 weiPerUnitLink, bool isFeedStale) {
uint32 stalenessSeconds = s_stalenessSeconds;
uint256 timestamp;
(, weiPerUnitLink, , timestamp, ) = i_link_native_feed.latestRoundData();
// solhint-disable-next-line not-rely-on-time
isFeedStale = stalenessSeconds > 0 && stalenessSeconds < block.timestamp - timestamp;
if (isFeedStale) {
weiPerUnitLink = s_fallbackWeiPerUnitLink;
}
// solhint-disable-next-line gas-custom-errors
require(weiPerUnitLink >= 0, "Invalid LINK wei price");
return (weiPerUnitLink, isFeedStale);
}
/**
* @dev Calculates extra amount of gas required for running an assembly call() post-EIP150.
*/
function _getEIP150Overhead(uint32 gas) private pure returns (uint32) {
return gas / 63 + 1;
}
function _getCoordinatorGasOverhead(uint32 numWords, bool nativePayment) internal view returns (uint32) {
if (nativePayment) {
return s_coordinatorGasOverheadNative + numWords * s_coordinatorGasOverheadPerWord;
} else {
return s_coordinatorGasOverheadLink + numWords * s_coordinatorGasOverheadPerWord;
}
}
/**
* @dev calls target address with exactly gasAmount gas and data as calldata
* or reverts if at least gasAmount gas is not available.
*/
function _callWithExactGas(uint256 gasAmount, address target, bytes memory data) private returns (bool success) {
assembly {
let g := gas()
// Compute g -= GAS_FOR_CALL_EXACT_CHECK and check for underflow
// The gas actually passed to the callee is min(gasAmount, 63//64*gas available).
// We want to ensure that we revert if gasAmount > 63//64*gas available
// as we do not want to provide them with less, however that check itself costs
// gas. GAS_FOR_CALL_EXACT_CHECK ensures we have at least enough gas to be able
// to revert if gasAmount > 63//64*gas available.
if lt(g, GAS_FOR_CALL_EXACT_CHECK) {
revert(0, 0)
}
g := sub(g, GAS_FOR_CALL_EXACT_CHECK)
// if g - g//64 <= gasAmount, revert
// (we subtract g//64 because of EIP-150)
if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
revert(0, 0)
}
// solidity calls check that a contract actually exists at the destination, so we do the same
if iszero(extcodesize(target)) {
revert(0, 0)
}
// call and return whether we succeeded. ignore return data
// call(gas,addr,value,argsOffset,argsLength,retOffset,retLength)
success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
return success;
}
function typeAndVersion() external pure virtual override returns (string memory) {
return "VRFV2PlusWrapper 1.0.0";
}
modifier onlyConfiguredNotDisabled() {
// solhint-disable-next-line gas-custom-errors
require(s_configured, "wrapper is not configured");
// solhint-disable-next-line gas-custom-errors
require(!s_disabled, "wrapper is disabled");
_;
}
}