How to Use Randomness in Smart Contracts -- Chainlink VRF
Contents
-
Refer to Chainlink VRF for more details.
-
Check the coordinator contract on the sepolia testnet.
Create and fund subscription
-
Subscription is needed to implement payment mechanism
-
You have to send native currency to coordinator contract before receiving randomness from it
-
// Create subscription function of coordinator contract function createSubscription() external override nonReentrant returns (uint256 subId) { // Generate a subscription id that is globally unique. uint64 currentSubNonce = s_currentSubNonce; subId = uint256( keccak256(abi.encodePacked(msg.sender, blockhash(block.number - 1), address(this), currentSubNonce)) ); // Increment the subscription nonce counter. s_currentSubNonce = currentSubNonce + 1; address[] memory consumers = new address[](0); s_subscriptions[subId] = Subscription({balance: 0, nativeBalance: 0, reqCount: 0}); s_subscriptionConfigs[subId] = SubscriptionConfig({ owner: msg.sender, requestedOwner: address(0), consumers: consumers }); s_subIds.add(subId); emit SubscriptionCreated(subId, msg.sender); return subId; } // Create subscription function of coordinator contract function fundSubscriptionWithNative(uint256 subId) external payable override nonReentrant { if (s_subscriptionConfigs[subId].owner == address(0)) { revert InvalidSubscription(); } uint256 oldNativeBalance = s_subscriptions[subId].nativeBalance; s_subscriptions[subId].nativeBalance += uint96(msg.value); s_totalNativeBalance += uint96(msg.value); emit SubscriptionFundedWithNative(subId, oldNativeBalance, oldNativeBalance + msg.value); }
Deploy your consumer contract
-
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; contract Consumer is VRFConsumerBaseV2Plus { uint256 immutable s_subscriptionId; bytes32 immutable s_keyHash; uint256 public s_requestId; /** * @param subscriptionId - the subscription ID that this contract uses for funding requests * @param coordinator - check https://docs.chain.link/vrf/v2-5/supported-networks * @param keyHash - the gas lane to use, which specifies the maximum gas price to bump to, check https://docs.chain.link/docs/vrf-contracts/#configurations */ constructor( uint256 subscriptionId, address coordinator, bytes32 keyHash ) VRFConsumerBaseV2Plus(coordinator) { s_keyHash = keyHash; s_subscriptionId = subscriptionId; } function requestRandomWords() external onlyOwner { s_requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: s_keyHash, subId: s_subscriptionId, requestConfirmations: 3, callbackGasLimit: 100000, numWords: 2, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({nativePayment: false}) ) }) ); } /** * @param - id of the request * @param randomWords - array of random results from the coordinator */ function fulfillRandomWords( uint256 /* requestId */, uint256[] calldata randomWords ) internal override { // Your logic to use randomness here } }
Add the consumer contract to the coordinator contract
-
// Add Consumer function of coordinator contract function addConsumer(uint256 subId, address consumer) external override onlySubOwner(subId) nonReentrant { ConsumerConfig storage consumerConfig = s_consumers[consumer][subId]; if (consumerConfig.active) { return; } address[] storage consumers = s_subscriptionConfigs[subId].consumers; if (consumers.length == MAX_CONSUMERS) { revert TooManyConsumers(); } consumerConfig.active = true; consumers.push(consumer); emit SubscriptionConsumerAdded(subId, consumer); }
How to send the randomness results
- Call
Consumer.requestRandomWords VRFCoordinatorV2_5.requestRandomWordsis called andRandomWordsRequestedevent is emitted- The oracle keep listening to the event
- The oracle calls
VRFCoordinatorV2_5.fulfillRandomWordsafter the event is received - The coordinator contract finds out the consumer contract who requested the randomness
- The coordinator contract calls
Consumer.fulfillRandomWordsto send the randomness to the consumer contract
The coordinator contract saves the consumer contract and vice versa. So the two contracts can call each other