-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add vrf coordinator v2.5 mock (#12873)
* Add vrf coordinator v2.5 mock * Fix natspec complaining about inheritdoc references inexistent contract
- Loading branch information
1 parent
8cf34d2
commit 1e50bae
Showing
3 changed files
with
834 additions
and
0 deletions.
There are no files selected for viewing
268 changes: 268 additions & 0 deletions
268
contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
// SPDX-License-Identifier: MIT | ||
// A mock for testing code that relies on VRFCoordinatorV2_5. | ||
pragma solidity ^0.8.19; | ||
|
||
import {IVRFCoordinatorV2Plus, IVRFSubscriptionV2Plus} from "../dev/interfaces/IVRFCoordinatorV2Plus.sol"; | ||
import {VRFV2PlusClient} from "../dev/libraries/VRFV2PlusClient.sol"; | ||
import {SubscriptionAPI} from "../dev/SubscriptionAPI.sol"; | ||
import {VRFConsumerBaseV2Plus} from "../dev/VRFConsumerBaseV2Plus.sol"; | ||
|
||
contract VRFCoordinatorV2_5Mock is SubscriptionAPI, IVRFCoordinatorV2Plus { | ||
uint96 public immutable i_base_fee; | ||
uint96 public immutable i_gas_price; | ||
int256 public immutable i_wei_per_unit_link; | ||
|
||
error InvalidRequest(); | ||
error InvalidRandomWords(); | ||
error InvalidExtraArgsTag(); | ||
error NotImplemented(); | ||
|
||
event RandomWordsRequested( | ||
bytes32 indexed keyHash, | ||
uint256 requestId, | ||
uint256 preSeed, | ||
uint256 indexed subId, | ||
uint16 minimumRequestConfirmations, | ||
uint32 callbackGasLimit, | ||
uint32 numWords, | ||
bytes extraArgs, | ||
address indexed sender | ||
); | ||
event RandomWordsFulfilled( | ||
uint256 indexed requestId, | ||
uint256 outputSeed, | ||
uint256 indexed subId, | ||
uint96 payment, | ||
bool nativePayment, | ||
bool success, | ||
bool onlyPremium | ||
); | ||
event ConfigSet(); | ||
|
||
uint64 internal s_currentSubId; | ||
uint256 internal s_nextRequestId = 1; | ||
uint256 internal s_nextPreSeed = 100; | ||
|
||
struct Request { | ||
uint256 subId; | ||
uint32 callbackGasLimit; | ||
uint32 numWords; | ||
bytes extraArgs; | ||
} | ||
mapping(uint256 => Request) internal s_requests; /* requestId */ /* request */ | ||
|
||
constructor(uint96 _baseFee, uint96 _gasPrice, int256 _weiPerUnitLink) SubscriptionAPI() { | ||
i_base_fee = _baseFee; | ||
i_gas_price = _gasPrice; | ||
i_wei_per_unit_link = _weiPerUnitLink; | ||
setConfig(); | ||
} | ||
|
||
/** | ||
* @notice Sets the configuration of the vrfv2 mock coordinator | ||
*/ | ||
function setConfig() public onlyOwner { | ||
s_config = Config({ | ||
minimumRequestConfirmations: 0, | ||
maxGasLimit: 0, | ||
stalenessSeconds: 0, | ||
gasAfterPaymentCalculation: 0, | ||
reentrancyLock: false, | ||
fulfillmentFlatFeeNativePPM: 0, | ||
fulfillmentFlatFeeLinkDiscountPPM: 0, | ||
nativePremiumPercentage: 0, | ||
linkPremiumPercentage: 0 | ||
}); | ||
emit ConfigSet(); | ||
} | ||
|
||
function consumerIsAdded(uint256 _subId, address _consumer) public view returns (bool) { | ||
return s_consumers[_consumer][_subId].active; | ||
} | ||
|
||
modifier onlyValidConsumer(uint256 _subId, address _consumer) { | ||
if (!consumerIsAdded(_subId, _consumer)) { | ||
revert InvalidConsumer(_subId, _consumer); | ||
} | ||
_; | ||
} | ||
|
||
/** | ||
* @notice fulfillRandomWords fulfills the given request, sending the random words to the supplied | ||
* @notice consumer. | ||
* | ||
* @dev This mock uses a simplified formula for calculating payment amount and gas usage, and does | ||
* @dev not account for all edge cases handled in the real VRF coordinator. When making requests | ||
* @dev against the real coordinator a small amount of additional LINK is required. | ||
* | ||
* @param _requestId the request to fulfill | ||
* @param _consumer the VRF randomness consumer to send the result to | ||
*/ | ||
function fulfillRandomWords(uint256 _requestId, address _consumer) external nonReentrant { | ||
fulfillRandomWordsWithOverride(_requestId, _consumer, new uint256[](0)); | ||
} | ||
|
||
/** | ||
* @notice fulfillRandomWordsWithOverride allows the user to pass in their own random words. | ||
* | ||
* @param _requestId the request to fulfill | ||
* @param _consumer the VRF randomness consumer to send the result to | ||
* @param _words user-provided random words | ||
*/ | ||
function fulfillRandomWordsWithOverride(uint256 _requestId, address _consumer, uint256[] memory _words) public { | ||
uint256 startGas = gasleft(); | ||
if (s_requests[_requestId].subId == 0) { | ||
revert InvalidRequest(); | ||
} | ||
Request memory req = s_requests[_requestId]; | ||
|
||
if (_words.length == 0) { | ||
_words = new uint256[](req.numWords); | ||
for (uint256 i = 0; i < req.numWords; i++) { | ||
_words[i] = uint256(keccak256(abi.encode(_requestId, i))); | ||
} | ||
} else if (_words.length != req.numWords) { | ||
revert InvalidRandomWords(); | ||
} | ||
|
||
VRFConsumerBaseV2Plus v; | ||
bytes memory callReq = abi.encodeWithSelector(v.rawFulfillRandomWords.selector, _requestId, _words); | ||
s_config.reentrancyLock = true; | ||
// solhint-disable-next-line avoid-low-level-calls, no-unused-vars | ||
(bool success, ) = _consumer.call{gas: req.callbackGasLimit}(callReq); | ||
s_config.reentrancyLock = false; | ||
|
||
bool nativePayment = uint8(req.extraArgs[req.extraArgs.length - 1]) == 1; | ||
|
||
uint256 rawPayment = i_base_fee + ((startGas - gasleft()) * i_gas_price); | ||
if (!nativePayment) { | ||
rawPayment = (1e18 * rawPayment) / uint256(i_wei_per_unit_link); | ||
} | ||
uint96 payment = uint96(rawPayment); | ||
|
||
_chargePayment(payment, nativePayment, req.subId); | ||
|
||
delete (s_requests[_requestId]); | ||
emit RandomWordsFulfilled(_requestId, _requestId, req.subId, payment, nativePayment, success, false); | ||
} | ||
|
||
function _chargePayment(uint96 payment, bool nativePayment, uint256 subId) internal { | ||
Subscription storage subcription = s_subscriptions[subId]; | ||
if (nativePayment) { | ||
uint96 prevBal = subcription.nativeBalance; | ||
if (prevBal < payment) { | ||
revert InsufficientBalance(); | ||
} | ||
subcription.nativeBalance = prevBal - payment; | ||
s_withdrawableNative += payment; | ||
} else { | ||
uint96 prevBal = subcription.balance; | ||
if (prevBal < payment) { | ||
revert InsufficientBalance(); | ||
} | ||
subcription.balance = prevBal - payment; | ||
s_withdrawableTokens += payment; | ||
} | ||
} | ||
|
||
/** | ||
* @notice fundSubscription allows funding a subscription with an arbitrary amount for testing. | ||
* | ||
* @param _subId the subscription to fund | ||
* @param _amount the amount to fund | ||
*/ | ||
function fundSubscription(uint256 _subId, uint256 _amount) public { | ||
if (s_subscriptionConfigs[_subId].owner == address(0)) { | ||
revert InvalidSubscription(); | ||
} | ||
uint256 oldBalance = s_subscriptions[_subId].balance; | ||
s_subscriptions[_subId].balance += uint96(_amount); | ||
s_totalBalance += uint96(_amount); | ||
emit SubscriptionFunded(_subId, oldBalance, oldBalance + _amount); | ||
} | ||
|
||
/// @dev Convert the extra args bytes into a struct | ||
/// @param extraArgs The extra args bytes | ||
/// @return The extra args struct | ||
function _fromBytes(bytes calldata extraArgs) internal pure returns (VRFV2PlusClient.ExtraArgsV1 memory) { | ||
if (extraArgs.length == 0) { | ||
return VRFV2PlusClient.ExtraArgsV1({nativePayment: false}); | ||
} | ||
if (bytes4(extraArgs) != VRFV2PlusClient.EXTRA_ARGS_V1_TAG) revert InvalidExtraArgsTag(); | ||
return abi.decode(extraArgs[4:], (VRFV2PlusClient.ExtraArgsV1)); | ||
} | ||
|
||
function requestRandomWords( | ||
VRFV2PlusClient.RandomWordsRequest calldata _req | ||
) external override nonReentrant onlyValidConsumer(_req.subId, msg.sender) returns (uint256) { | ||
uint256 subId = _req.subId; | ||
if (s_subscriptionConfigs[subId].owner == address(0)) { | ||
revert InvalidSubscription(); | ||
} | ||
|
||
uint256 requestId = s_nextRequestId++; | ||
uint256 preSeed = s_nextPreSeed++; | ||
|
||
bytes memory extraArgsBytes = VRFV2PlusClient._argsToBytes(_fromBytes(_req.extraArgs)); | ||
s_requests[requestId] = Request({ | ||
subId: _req.subId, | ||
callbackGasLimit: _req.callbackGasLimit, | ||
numWords: _req.numWords, | ||
extraArgs: _req.extraArgs | ||
}); | ||
|
||
emit RandomWordsRequested( | ||
_req.keyHash, | ||
requestId, | ||
preSeed, | ||
_req.subId, | ||
_req.requestConfirmations, | ||
_req.callbackGasLimit, | ||
_req.numWords, | ||
extraArgsBytes, | ||
msg.sender | ||
); | ||
return requestId; | ||
} | ||
|
||
/** | ||
* @inheritdoc IVRFSubscriptionV2Plus | ||
*/ | ||
function removeConsumer( | ||
uint256 _subId, | ||
address _consumer | ||
) external override onlySubOwner(_subId) onlyValidConsumer(_subId, _consumer) nonReentrant { | ||
if (!s_consumers[_consumer][_subId].active) { | ||
revert InvalidConsumer(_subId, _consumer); | ||
} | ||
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]; | ||
s_subscriptionConfigs[_subId].consumers[i] = last; | ||
s_subscriptionConfigs[_subId].consumers.pop(); | ||
break; | ||
} | ||
} | ||
s_consumers[_consumer][_subId].active = false; | ||
emit SubscriptionConsumerRemoved(_subId, _consumer); | ||
} | ||
|
||
/** | ||
* @inheritdoc IVRFSubscriptionV2Plus | ||
*/ | ||
function cancelSubscription(uint256 _subId, address _to) external override onlySubOwner(_subId) nonReentrant { | ||
(uint96 balance, uint96 nativeBalance) = _deleteSubscription(_subId); | ||
|
||
(bool success, ) = _to.call{value: uint256(nativeBalance)}(""); | ||
if (!success) { | ||
revert FailedToSendNative(); | ||
} | ||
emit SubscriptionCanceled(_subId, _to, balance, nativeBalance); | ||
} | ||
|
||
function pendingRequestExists(uint256 /*subId*/) public pure override returns (bool) { | ||
revert NotImplemented(); | ||
} | ||
} |
Oops, something went wrong.