Contents

How to Use Randomness in Smart Contracts -- Chainlink VRF

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.requestRandomWords is called and RandomWordsRequested event is emitted
  • The oracle keep listening to the event
  • The oracle calls VRFCoordinatorV2_5.fulfillRandomWords after the event is received
  • The coordinator contract finds out the consumer contract who requested the randomness
  • The coordinator contract calls Consumer.fulfillRandomWords to 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