-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
VRFCoordinatorV2.sol
840 lines (790 loc) · 31.5 KB
/
VRFCoordinatorV2.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
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {LinkTokenInterface} from "../shared/interfaces/LinkTokenInterface.sol";
import {BlockhashStoreInterface} from "./interfaces/BlockhashStoreInterface.sol";
import {AggregatorV3Interface} from "../shared/interfaces/AggregatorV3Interface.sol";
import {VRFCoordinatorV2Interface} from "./interfaces/VRFCoordinatorV2Interface.sol";
import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
import {IERC677Receiver} from "../shared/interfaces/IERC677Receiver.sol";
import {VRF} from "./VRF.sol";
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
import {VRFConsumerBaseV2} from "./VRFConsumerBaseV2.sol";
import {ChainSpecificUtil} from "../ChainSpecificUtil_v0_8_6.sol";
contract VRFCoordinatorV2 is VRF, ConfirmedOwner, TypeAndVersionInterface, VRFCoordinatorV2Interface, IERC677Receiver {
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
LinkTokenInterface public immutable LINK;
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
AggregatorV3Interface public immutable LINK_ETH_FEED;
// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
BlockhashStoreInterface public immutable BLOCKHASH_STORE;
// We need to maintain a list of consuming addresses.
// This bound ensures we are able to loop over them as needed.
// Should a user require more consumers, they can use multiple subscriptions.
uint16 public constant MAX_CONSUMERS = 100;
error TooManyConsumers();
error InsufficientBalance();
error InvalidConsumer(uint64 subId, address consumer);
error InvalidSubscription();
error OnlyCallableFromLink();
error InvalidCalldata();
error MustBeSubOwner(address owner);
error PendingRequestExists();
error MustBeRequestedOwner(address proposedOwner);
error BalanceInvariantViolated(uint256 internalBalance, uint256 externalBalance); // Should never happen
event FundsRecovered(address to, uint256 amount);
// We use the subscription struct (1 word)
// at fulfillment time.
struct Subscription {
// There are only 1e9*1e18 = 1e27 juels in existence, so the balance can fit in uint96 (2^96 ~ 7e28)
uint96 balance; // Common link balance used for all consumer requests.
uint64 reqCount; // For fee tiers
}
// We use the config for the mgmt APIs
struct SubscriptionConfig {
address owner; // Owner can fund/withdraw/cancel the sub.
address requestedOwner; // For safely transferring sub ownership.
// Maintains the list of keys in s_consumers.
// We do this for 2 reasons:
// 1. To be able to clean up all keys from s_consumers when canceling a subscription.
// 2. To be able to return the list of all consumers in getSubscription.
// Note that we need the s_consumers map to be able to directly check if a
// consumer is valid without reading all the consumers from storage.
address[] consumers;
}
// Note a nonce of 0 indicates an the consumer is not assigned to that subscription.
mapping(address => mapping(uint64 => uint64)) /* consumer */ /* subId */ /* nonce */ private s_consumers;
mapping(uint64 => SubscriptionConfig) /* subId */ /* subscriptionConfig */ private s_subscriptionConfigs;
mapping(uint64 => Subscription) /* subId */ /* subscription */ private s_subscriptions;
// We make the sub count public so that its possible to
// get all the current subscriptions via getSubscription.
uint64 private s_currentSubId;
// s_totalBalance tracks the total link sent to/from
// this contract through onTokenTransfer, cancelSubscription and oracleWithdraw.
// A discrepancy with this contract's link balance indicates someone
// sent tokens using transfer and so we may need to use recoverFunds.
uint96 private s_totalBalance;
event SubscriptionCreated(uint64 indexed subId, address owner);
event SubscriptionFunded(uint64 indexed subId, uint256 oldBalance, uint256 newBalance);
event SubscriptionConsumerAdded(uint64 indexed subId, address consumer);
event SubscriptionConsumerRemoved(uint64 indexed subId, address consumer);
event SubscriptionCanceled(uint64 indexed subId, address to, uint256 amount);
event SubscriptionOwnerTransferRequested(uint64 indexed subId, address from, address to);
event SubscriptionOwnerTransferred(uint64 indexed subId, address from, address to);
// Set this maximum to 200 to give us a 56 block window to fulfill
// the request before requiring the block hash feeder.
uint16 public constant MAX_REQUEST_CONFIRMATIONS = 200;
uint32 public constant MAX_NUM_WORDS = 500;
// 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;
error InvalidRequestConfirmations(uint16 have, uint16 min, uint16 max);
error GasLimitTooBig(uint32 have, uint32 want);
error NumWordsTooBig(uint32 have, uint32 want);
error ProvingKeyAlreadyRegistered(bytes32 keyHash);
error NoSuchProvingKey(bytes32 keyHash);
error InvalidLinkWeiPrice(int256 linkWei);
error InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
error BlockhashNotInStore(uint256 blockNum);
error PaymentTooLarge();
error Reentrant();
struct RequestCommitment {
uint64 blockNum;
uint64 subId;
uint32 callbackGasLimit;
uint32 numWords;
address sender;
}
mapping(bytes32 => address) /* keyHash */ /* oracle */ private s_provingKeys;
bytes32[] private s_provingKeyHashes;
mapping(address => uint96) /* oracle */ /* LINK balance */ private s_withdrawableTokens;
mapping(uint256 => bytes32) /* requestID */ /* commitment */ private s_requestCommitments;
event ProvingKeyRegistered(bytes32 keyHash, address indexed oracle);
event ProvingKeyDeregistered(bytes32 keyHash, address indexed oracle);
event RandomWordsRequested(
bytes32 indexed keyHash,
uint256 requestId,
uint256 preSeed,
uint64 indexed subId,
uint16 minimumRequestConfirmations,
uint32 callbackGasLimit,
uint32 numWords,
address indexed sender
);
event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint96 payment, bool success);
struct Config {
uint16 minimumRequestConfirmations;
uint32 maxGasLimit;
// Reentrancy protection.
bool reentrancyLock;
// stalenessSeconds is how long before we consider the feed price to be stale
// and fallback to fallbackWeiPerUnitLink.
uint32 stalenessSeconds;
// Gas to cover oracle payment after we calculate the payment.
// We make it configurable in case those operations are repriced.
uint32 gasAfterPaymentCalculation;
}
int256 private s_fallbackWeiPerUnitLink;
Config private s_config;
FeeConfig private s_feeConfig;
struct FeeConfig {
// Flat fee charged per fulfillment in millionths of link
// So fee range is [0, 2^32/10^6].
uint32 fulfillmentFlatFeeLinkPPMTier1;
uint32 fulfillmentFlatFeeLinkPPMTier2;
uint32 fulfillmentFlatFeeLinkPPMTier3;
uint32 fulfillmentFlatFeeLinkPPMTier4;
uint32 fulfillmentFlatFeeLinkPPMTier5;
uint24 reqsForTier2;
uint24 reqsForTier3;
uint24 reqsForTier4;
uint24 reqsForTier5;
}
event ConfigSet(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig feeConfig
);
constructor(address link, address blockhashStore, address linkEthFeed) ConfirmedOwner(msg.sender) {
LINK = LinkTokenInterface(link);
LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed);
BLOCKHASH_STORE = BlockhashStoreInterface(blockhashStore);
}
/**
* @notice Registers a proving key to an oracle.
* @param oracle address of the oracle
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
if (s_provingKeys[kh] != address(0)) {
revert ProvingKeyAlreadyRegistered(kh);
}
s_provingKeys[kh] = oracle;
s_provingKeyHashes.push(kh);
emit ProvingKeyRegistered(kh, oracle);
}
/**
* @notice Deregisters a proving key to an oracle.
* @param publicProvingKey key that oracle can use to submit vrf fulfillments
*/
function deregisterProvingKey(uint256[2] calldata publicProvingKey) external onlyOwner {
bytes32 kh = hashOfKey(publicProvingKey);
address oracle = s_provingKeys[kh];
if (oracle == address(0)) {
revert NoSuchProvingKey(kh);
}
delete s_provingKeys[kh];
for (uint256 i = 0; i < s_provingKeyHashes.length; i++) {
if (s_provingKeyHashes[i] == kh) {
bytes32 last = s_provingKeyHashes[s_provingKeyHashes.length - 1];
// Copy last element and overwrite kh to be deleted with it
s_provingKeyHashes[i] = last;
s_provingKeyHashes.pop();
}
}
emit ProvingKeyDeregistered(kh, oracle);
}
/**
* @notice Returns the proving key hash key associated with this public key
* @param publicKey the key to return the hash of
*/
function hashOfKey(uint256[2] memory publicKey) public pure returns (bytes32) {
return keccak256(abi.encode(publicKey));
}
/**
* @notice Sets the configuration of the vrfv2 coordinator
* @param minimumRequestConfirmations global min for request confirmations
* @param maxGasLimit global max for request gas limit
* @param stalenessSeconds if the eth/link feed is more stale then this, use the fallback price
* @param gasAfterPaymentCalculation gas used in doing accounting after completing the gas measurement
* @param fallbackWeiPerUnitLink fallback eth/link price in the case of a stale feed
* @param feeConfig fee tier configuration
*/
function setConfig(
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation,
int256 fallbackWeiPerUnitLink,
FeeConfig memory feeConfig
) external onlyOwner {
if (minimumRequestConfirmations > MAX_REQUEST_CONFIRMATIONS) {
revert InvalidRequestConfirmations(
minimumRequestConfirmations,
minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
if (fallbackWeiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(fallbackWeiPerUnitLink);
}
s_config = Config({
minimumRequestConfirmations: minimumRequestConfirmations,
maxGasLimit: maxGasLimit,
stalenessSeconds: stalenessSeconds,
gasAfterPaymentCalculation: gasAfterPaymentCalculation,
reentrancyLock: false
});
s_feeConfig = feeConfig;
s_fallbackWeiPerUnitLink = fallbackWeiPerUnitLink;
emit ConfigSet(
minimumRequestConfirmations,
maxGasLimit,
stalenessSeconds,
gasAfterPaymentCalculation,
fallbackWeiPerUnitLink,
s_feeConfig
);
}
function getConfig()
external
view
returns (
uint16 minimumRequestConfirmations,
uint32 maxGasLimit,
uint32 stalenessSeconds,
uint32 gasAfterPaymentCalculation
)
{
return (
s_config.minimumRequestConfirmations,
s_config.maxGasLimit,
s_config.stalenessSeconds,
s_config.gasAfterPaymentCalculation
);
}
function getFeeConfig()
external
view
returns (
uint32 fulfillmentFlatFeeLinkPPMTier1,
uint32 fulfillmentFlatFeeLinkPPMTier2,
uint32 fulfillmentFlatFeeLinkPPMTier3,
uint32 fulfillmentFlatFeeLinkPPMTier4,
uint32 fulfillmentFlatFeeLinkPPMTier5,
uint24 reqsForTier2,
uint24 reqsForTier3,
uint24 reqsForTier4,
uint24 reqsForTier5
)
{
return (
s_feeConfig.fulfillmentFlatFeeLinkPPMTier1,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier2,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier3,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier4,
s_feeConfig.fulfillmentFlatFeeLinkPPMTier5,
s_feeConfig.reqsForTier2,
s_feeConfig.reqsForTier3,
s_feeConfig.reqsForTier4,
s_feeConfig.reqsForTier5
);
}
function getTotalBalance() external view returns (uint256) {
return s_totalBalance;
}
function getFallbackWeiPerUnitLink() external view returns (int256) {
return s_fallbackWeiPerUnitLink;
}
/**
* @notice Owner cancel subscription, sends remaining link directly to the subscription owner.
* @param subId subscription id
* @dev notably can be called even if there are pending requests, outstanding ones may fail onchain
*/
function ownerCancelSubscription(uint64 subId) external onlyOwner {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
_cancelSubscriptionHelper(subId, s_subscriptionConfigs[subId].owner);
}
/**
* @notice Recover link sent with transfer instead of transferAndCall.
* @param to address to send link to
*/
function recoverFunds(address to) external onlyOwner {
uint256 externalBalance = LINK.balanceOf(address(this));
uint256 internalBalance = uint256(s_totalBalance);
if (internalBalance > externalBalance) {
revert BalanceInvariantViolated(internalBalance, externalBalance);
}
if (internalBalance < externalBalance) {
uint256 amount = externalBalance - internalBalance;
LINK.transfer(to, amount);
emit FundsRecovered(to, amount);
}
// If the balances are equal, nothing to be done.
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function getRequestConfig() external view override returns (uint16, uint32, bytes32[] memory) {
return (s_config.minimumRequestConfirmations, s_config.maxGasLimit, s_provingKeyHashes);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function requestRandomWords(
bytes32 keyHash,
uint64 subId,
uint16 requestConfirmations,
uint32 callbackGasLimit,
uint32 numWords
) external override nonReentrant returns (uint256) {
// Input validation using the subscription storage.
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// Its important to ensure that the consumer is in fact who they say they
// are, otherwise they could use someone else's subscription balance.
// A nonce of 0 indicates consumer is not allocated to the sub.
uint64 currentNonce = s_consumers[msg.sender][subId];
if (currentNonce == 0) {
revert InvalidConsumer(subId, msg.sender);
}
// Input validation using the config storage word.
if (
requestConfirmations < s_config.minimumRequestConfirmations || requestConfirmations > MAX_REQUEST_CONFIRMATIONS
) {
revert InvalidRequestConfirmations(
requestConfirmations,
s_config.minimumRequestConfirmations,
MAX_REQUEST_CONFIRMATIONS
);
}
// No lower bound on the requested gas limit. A user could request 0
// and they would simply be billed for the proof verification and wouldn't be
// able to do anything with the random value.
if (callbackGasLimit > s_config.maxGasLimit) {
revert GasLimitTooBig(callbackGasLimit, s_config.maxGasLimit);
}
if (numWords > MAX_NUM_WORDS) {
revert NumWordsTooBig(numWords, MAX_NUM_WORDS);
}
// Note we do not check whether the keyHash is valid to save gas.
// The consequence for users is that they can send requests
// for invalid keyHashes which will simply not be fulfilled.
uint64 nonce = currentNonce + 1;
(uint256 requestId, uint256 preSeed) = _computeRequestId(keyHash, msg.sender, subId, nonce);
s_requestCommitments[requestId] = keccak256(
abi.encode(requestId, ChainSpecificUtil._getBlockNumber(), subId, callbackGasLimit, numWords, msg.sender)
);
emit RandomWordsRequested(
keyHash,
requestId,
preSeed,
subId,
requestConfirmations,
callbackGasLimit,
numWords,
msg.sender
);
s_consumers[msg.sender][subId] = nonce;
return requestId;
}
/**
* @notice Get request commitment
* @param requestId id of request
* @dev used to determine if a request is fulfilled or not
*/
function getCommitment(uint256 requestId) external view returns (bytes32) {
return s_requestCommitments[requestId];
}
function _computeRequestId(
bytes32 keyHash,
address sender,
uint64 subId,
uint64 nonce
) private pure returns (uint256, uint256) {
uint256 preSeed = uint256(keccak256(abi.encode(keyHash, sender, subId, nonce)));
return (uint256(keccak256(abi.encode(keyHash, preSeed))), preSeed);
}
/**
* @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 _getRandomnessFromProof(
Proof calldata proof,
RequestCommitment memory rc
) private view returns (bytes32 keyHash, uint256 requestId, uint256 randomness) {
keyHash = hashOfKey(proof.pk);
// Only registered proving keys are permitted.
address oracle = s_provingKeys[keyHash];
if (oracle == address(0)) {
revert NoSuchProvingKey(keyHash);
}
requestId = uint256(keccak256(abi.encode(keyHash, proof.seed)));
bytes32 commitment = s_requestCommitments[requestId];
if (commitment == 0) {
revert NoCorrespondingRequest();
}
if (
commitment != keccak256(abi.encode(requestId, rc.blockNum, rc.subId, rc.callbackGasLimit, rc.numWords, rc.sender))
) {
revert IncorrectCommitment();
}
bytes32 blockHash = ChainSpecificUtil._getBlockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
blockHash = BLOCKHASH_STORE.getBlockhash(rc.blockNum);
if (blockHash == bytes32(0)) {
revert BlockhashNotInStore(rc.blockNum);
}
}
// The seed actually used by the VRF machinery, mixing in the blockhash
uint256 actualSeed = uint256(keccak256(abi.encodePacked(proof.seed, blockHash)));
randomness = VRF._randomValueFromVRFProof(proof, actualSeed); // Reverts on failure
return (keyHash, requestId, randomness);
}
/*
* @notice Compute fee based on the request count
* @param reqCount number of requests
* @return feePPM fee in LINK PPM
*/
function getFeeTier(uint64 reqCount) public view returns (uint32) {
FeeConfig memory fc = s_feeConfig;
if (0 <= reqCount && reqCount <= fc.reqsForTier2) {
return fc.fulfillmentFlatFeeLinkPPMTier1;
}
if (fc.reqsForTier2 < reqCount && reqCount <= fc.reqsForTier3) {
return fc.fulfillmentFlatFeeLinkPPMTier2;
}
if (fc.reqsForTier3 < reqCount && reqCount <= fc.reqsForTier4) {
return fc.fulfillmentFlatFeeLinkPPMTier3;
}
if (fc.reqsForTier4 < reqCount && reqCount <= fc.reqsForTier5) {
return fc.fulfillmentFlatFeeLinkPPMTier4;
}
return fc.fulfillmentFlatFeeLinkPPMTier5;
}
/*
* @notice Fulfill a randomness request
* @param proof contains the proof and randomness
* @param rc request commitment pre-image, committed to at request time
* @return payment amount billed to the subscription
* @dev simulated offchain to determine if sufficient balance is present to fulfill the request
*/
function fulfillRandomWords(
Proof calldata proof,
RequestCommitment memory rc
) external nonReentrant returns (uint96) {
uint256 startGas = gasleft();
(bytes32 keyHash, uint256 requestId, uint256 randomness) = _getRandomnessFromProof(proof, rc);
uint256[] memory randomWords = new uint256[](rc.numWords);
for (uint256 i = 0; i < rc.numWords; i++) {
randomWords[i] = uint256(keccak256(abi.encode(randomness, i)));
}
delete s_requestCommitments[requestId];
VRFConsumerBaseV2 v;
bytes memory resp = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, requestId, randomWords);
// Call with explicitly the amount of callback gas requested
// Important to not let them exhaust the gas budget and avoid oracle payment.
// Do not allow any non-view/non-pure coordinator functions to be called
// during the consumers callback code via reentrancyLock.
// Note that _callWithExactGas will revert if we do not have sufficient gas
// to give the callee their requested amount.
s_config.reentrancyLock = true;
bool success = _callWithExactGas(rc.callbackGasLimit, rc.sender, resp);
s_config.reentrancyLock = false;
// Increment the req count for fee tier selection.
uint64 reqCount = s_subscriptions[rc.subId].reqCount;
s_subscriptions[rc.subId].reqCount += 1;
// We want to charge users exactly for how much gas they use in their callback.
// The gasAfterPaymentCalculation is meant to cover these additional operations where we
// decrement the subscription balance and increment the oracles withdrawable balance.
// We also add the flat link fee to the payment amount.
// Its specified in millionths of link, if s_config.fulfillmentFlatFeeLinkPPM = 1
// 1 link / 1e6 = 1e18 juels / 1e6 = 1e12 juels.
uint96 payment = _calculatePaymentAmount(
startGas,
s_config.gasAfterPaymentCalculation,
getFeeTier(reqCount),
tx.gasprice
);
if (s_subscriptions[rc.subId].balance < payment) {
revert InsufficientBalance();
}
s_subscriptions[rc.subId].balance -= payment;
s_withdrawableTokens[s_provingKeys[keyHash]] += payment;
// Include payment in the event for tracking costs.
emit RandomWordsFulfilled(requestId, randomness, payment, success);
return payment;
}
// Get the amount of gas used for fulfillment
function _calculatePaymentAmount(
uint256 startGas,
uint256 gasAfterPaymentCalculation,
uint32 fulfillmentFlatFeeLinkPPM,
uint256 weiPerUnitGas
) internal view returns (uint96) {
int256 weiPerUnitLink;
weiPerUnitLink = _getFeedData();
if (weiPerUnitLink <= 0) {
revert InvalidLinkWeiPrice(weiPerUnitLink);
}
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data);
// (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
uint256(weiPerUnitLink);
uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM);
if (paymentNoFee > (1e27 - fee)) {
revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence.
}
return uint96(paymentNoFee + fee);
}
function _getFeedData() private view returns (int256) {
uint32 stalenessSeconds = s_config.stalenessSeconds;
bool staleFallback = stalenessSeconds > 0;
uint256 timestamp;
int256 weiPerUnitLink;
(, weiPerUnitLink, , timestamp, ) = LINK_ETH_FEED.latestRoundData();
// solhint-disable-next-line not-rely-on-time
if (staleFallback && stalenessSeconds < block.timestamp - timestamp) {
weiPerUnitLink = s_fallbackWeiPerUnitLink;
}
return weiPerUnitLink;
}
/*
* @notice Oracle withdraw LINK earned through fulfilling requests
* @param recipient where to send the funds
* @param amount amount to withdraw
*/
function oracleWithdraw(address recipient, uint96 amount) external nonReentrant {
if (s_withdrawableTokens[msg.sender] < amount) {
revert InsufficientBalance();
}
s_withdrawableTokens[msg.sender] -= amount;
s_totalBalance -= amount;
if (!LINK.transfer(recipient, amount)) {
revert InsufficientBalance();
}
}
function onTokenTransfer(address /* sender */, uint256 amount, bytes calldata data) external override nonReentrant {
if (msg.sender != address(LINK)) {
revert OnlyCallableFromLink();
}
if (data.length != 32) {
revert InvalidCalldata();
}
uint64 subId = abi.decode(data, (uint64));
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
// We do not check that the msg.sender is the subscription owner,
// anyone can fund a subscription.
uint256 oldBalance = s_subscriptions[subId].balance;
s_subscriptions[subId].balance += uint96(amount);
s_totalBalance += uint96(amount);
emit SubscriptionFunded(subId, oldBalance, oldBalance + amount);
}
function getCurrentSubId() external view returns (uint64) {
return s_currentSubId;
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function getSubscription(
uint64 subId
) external view override returns (uint96 balance, uint64 reqCount, address owner, address[] memory consumers) {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
return (
s_subscriptions[subId].balance,
s_subscriptions[subId].reqCount,
s_subscriptionConfigs[subId].owner,
s_subscriptionConfigs[subId].consumers
);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function createSubscription() external override nonReentrant returns (uint64) {
s_currentSubId++;
uint64 currentSubId = s_currentSubId;
address[] memory consumers = new address[](0);
s_subscriptions[currentSubId] = Subscription({balance: 0, reqCount: 0});
s_subscriptionConfigs[currentSubId] = SubscriptionConfig({
owner: msg.sender,
requestedOwner: address(0),
consumers: consumers
});
emit SubscriptionCreated(currentSubId, msg.sender);
return currentSubId;
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function requestSubscriptionOwnerTransfer(
uint64 subId,
address newOwner
) external override onlySubOwner(subId) nonReentrant {
// Proposing to address(0) would never be claimable so don't need to check.
if (s_subscriptionConfigs[subId].requestedOwner != newOwner) {
s_subscriptionConfigs[subId].requestedOwner = newOwner;
emit SubscriptionOwnerTransferRequested(subId, msg.sender, newOwner);
}
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function acceptSubscriptionOwnerTransfer(uint64 subId) external override nonReentrant {
if (s_subscriptionConfigs[subId].owner == address(0)) {
revert InvalidSubscription();
}
if (s_subscriptionConfigs[subId].requestedOwner != msg.sender) {
revert MustBeRequestedOwner(s_subscriptionConfigs[subId].requestedOwner);
}
address oldOwner = s_subscriptionConfigs[subId].owner;
s_subscriptionConfigs[subId].owner = msg.sender;
s_subscriptionConfigs[subId].requestedOwner = address(0);
emit SubscriptionOwnerTransferred(subId, oldOwner, msg.sender);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function removeConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
if (pendingRequestExists(subId)) {
revert PendingRequestExists();
}
if (s_consumers[consumer][subId] == 0) {
revert InvalidConsumer(subId, consumer);
}
// Note bounded by MAX_CONSUMERS
address[] memory consumers = s_subscriptionConfigs[subId].consumers;
uint256 lastConsumerIndex = consumers.length - 1;
for (uint256 i = 0; i < consumers.length; i++) {
if (consumers[i] == consumer) {
address last = consumers[lastConsumerIndex];
// Storage write to preserve last element
s_subscriptionConfigs[subId].consumers[i] = last;
// Storage remove last element
s_subscriptionConfigs[subId].consumers.pop();
break;
}
}
delete s_consumers[consumer][subId];
emit SubscriptionConsumerRemoved(subId, consumer);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function addConsumer(uint64 subId, address consumer) external override onlySubOwner(subId) nonReentrant {
// Already maxed, cannot add any more consumers.
if (s_subscriptionConfigs[subId].consumers.length == MAX_CONSUMERS) {
revert TooManyConsumers();
}
if (s_consumers[consumer][subId] != 0) {
// Idempotence - do nothing if already added.
// Ensures uniqueness in s_subscriptions[subId].consumers.
return;
}
// Initialize the nonce to 1, indicating the consumer is allocated.
s_consumers[consumer][subId] = 1;
s_subscriptionConfigs[subId].consumers.push(consumer);
emit SubscriptionConsumerAdded(subId, consumer);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
*/
function cancelSubscription(uint64 subId, address to) external override onlySubOwner(subId) nonReentrant {
if (pendingRequestExists(subId)) {
revert PendingRequestExists();
}
_cancelSubscriptionHelper(subId, to);
}
function _cancelSubscriptionHelper(uint64 subId, address to) private nonReentrant {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
Subscription memory sub = s_subscriptions[subId];
uint96 balance = sub.balance;
// Note bounded by MAX_CONSUMERS;
// If no consumers, does nothing.
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
delete s_consumers[subConfig.consumers[i]][subId];
}
delete s_subscriptionConfigs[subId];
delete s_subscriptions[subId];
s_totalBalance -= balance;
if (!LINK.transfer(to, uint256(balance))) {
revert InsufficientBalance();
}
emit SubscriptionCanceled(subId, to, balance);
}
/**
* @inheritdoc VRFCoordinatorV2Interface
* @dev Looping is bounded to MAX_CONSUMERS*(number of keyhashes).
* @dev Used to disable subscription canceling while outstanding request are present.
*/
function pendingRequestExists(uint64 subId) public view override returns (bool) {
SubscriptionConfig memory subConfig = s_subscriptionConfigs[subId];
for (uint256 i = 0; i < subConfig.consumers.length; i++) {
for (uint256 j = 0; j < s_provingKeyHashes.length; j++) {
(uint256 reqId, ) = _computeRequestId(
s_provingKeyHashes[j],
subConfig.consumers[i],
subId,
s_consumers[subConfig.consumers[i]][subId]
);
if (s_requestCommitments[reqId] != 0) {
return true;
}
}
}
return false;
}
modifier onlySubOwner(uint64 subId) {
address owner = s_subscriptionConfigs[subId].owner;
if (owner == address(0)) {
revert InvalidSubscription();
}
if (msg.sender != owner) {
revert MustBeSubOwner(owner);
}
_;
}
modifier nonReentrant() {
if (s_config.reentrancyLock) {
revert Reentrant();
}
_;
}
/**
* @notice The type and version of this contract
* @return Type and version string
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "VRFCoordinatorV2 1.0.0";
}
}