本文最后更新于 2024-05-10,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准

Call the VRF By subscription method

Static Badge
Static Badge

Description

This is a sample record that calls the VRF contract to get a random number.

Steps

Deploy the VRF contract

  1. Go to the vrf.Chain.link website and create a new subsription.
    • Get the SubId of subsription.
    • Fund the subscription with Link/ETH. (Native payment is supported for VRF2.5 now)
  2. Deploy the VRF contract to the sepolia testnet.
    • Set the SubId, Coordinator address, and the gas lane.
    • Get the contract address from the deployment transaction.
  3. Add the contract address as consumer to the chainlink VRF subscription.

Call the VRF contract

  1. Go back to contract to call the requestRandomWords function.
    1. RequestRandomWords
      1. numWords: The number of random words to generate.
      2. nativePayment: Use Link or native token (ETH) as payment.
        1. True: Use Link as payment.
        2. False: Use native token (ETH) as payment.
      3. 因为我这个 contract 用的是 subscription 的方式,所以 Link/ETH 我已经充到了 subscription 里了,所以这里就不用你们给 Token 了。只用给 Gas Fee 来进行 Tx.
      4. 另外如果长时间没有得到 randomWords, 可以去 chainlink explorer 看看有没有报错。
        1. 如果是 Out of gas, 你们可以尝试增加 Gas limit.
        2. Set the gas limit to 500_000 or higher by setCallbackGaslimit.
        3. SetCallbackGaslimit
  2. Call the lastRequestId function to get the latest request ID.
    1. LastRequestId
  3. Call the getRequestStatus function to get the status of request ID.
    1. getRequestStatus
    2. fulfilled:
      • True for the request has been fulfilled with Chainlink VRF.
      • False for the request is still pending.
    3. randomWords:
      • The random words generated by the VRF contract.
      • Each random word is separated by a comma.

Call records

VRF contract address: 0x82E674688842b2d88D3968A932291Ef68107aC81

VRF2.5 SubId: 24513078928439205270705449808212503718043530537999925440364011035234675106258
Link to subscription

Define Cost as: (Minimum Subscription balance/ Actual cost of token)

Link & ETH BalanceGas limitRandom numberWorksRequest IDRequest timeNative PaymentLink costETH costSuccessReason
50 LINK, 2 ETH100_00016464056575753930518461622649453801761042818241217051200270706803227704261497725sFalse1.9727367053959264 LINK / 1.2635988206642421 LINK0TrueNone
48.73640117933575 LINK, 2 ETH100_00032350980022217299814958860474264640303050032694796670163558906682723552972900960sTrue00.009 ETH / 0.005555675699588659 ETHFalseOut of gas
48.73640117933575 LINK, 1.9944443243004113 ETH500_0003284745893644179156599825873346083314398774780660364061966815285562242446298460sTrue00.021 ETH / 0.00531652985517589 ETHTrueNone
48.73640117933575 LINK, 1.9891277944452355 ETH500_00066178808007736314547731110316373693521776639941476063169221203491047459559098845sFalse4.603052312590495 LINK / 1.7460505752181679 LINK0TrueNone
46.99035060411759 LINK, 1.9891277944452355 ETH500_00010112282426175383503677443689536875535439589644453268384906743245662470004949219120sTrue00.021 ETH / 0.013168056 ETHTrueNone
46.99035060411759 LINK, 1.9759597384452354 ETH500_000104670612677263360579520229416748962361626139289621463888525616048998534866685600sFalse4.596683277304416 LINK / 2.959053132874994 LINK0TrueNone

VRF Contract

// SPDX-License-Identifier: MIT

// Layout of Contract:
// version
// imports
// errors
// interfaces, libraries, contracts
// Type declarations
// State variables
// Events
// Modifiers
// Functions

// Layout of Functions:
// constructor
// receive function (if exists)
// fallback function (if exists)
// external
// public
// internal
// private
// internal & private view & pure functions
// external & public view & pure functions

pragma solidity ^0.8.6;

import {IVRFCoordinatorV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol";
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";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/* Errors */
error VRFConsumerV2_5__NoRequestFound();
error VRFConsumerV2_5__SubscriptionNotFunded();
error VRFConsumerV2_5__OutofRange();
error VRFConsumerV2_5__CollaboratorExists();
error VRFConsumerV2_5__CollaboratorNotExists();

/* Interfaces, Libraries, and Contracts */

contract VRFConsumerV2_5 is VRFConsumerBaseV2Plus {
    event RequestSent(uint256 indexed requestId, uint32 numWords);
    event RequestFulfilled(uint256 indexed requestId, uint256[] randomWords);
    event CollaboratorAdded(address indexed collaborator);
    event CollaboratorRemoved(address indexed collaborator);

    modifier onlyOwnerOrCollaborators() {
        if (msg.sender != owner() && s_collaborators[msg.sender]) {
            revert VRFConsumerV2_5__CollaboratorNotExists();
        }
        _;
    }

    // The RequestStatus struct stores the status of a request.
    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }

    IVRFCoordinatorV2Plus COORDINATOR;

    // Your subscription ID.
    uint256 s_subscriptionId;

    // The Coordinators address.
    /**
     * HARDCODED FOR SEPOLIA
     * COORDINATOR: 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B
     */
    address immutable s_coordinatorAddr;

    // The gas lane to use, which specifies the maximum gas price to bump to.
    // For a list of available gas lanes on each network,
    // see https://docs.chain.link/docs/vrf/v2-5/supported-networks
    // 30 gwei keyhash: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae
    bytes32 immutable s_keyhash;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Storing each word costs about 20,000 gas,
    // so 100,000 is a safe default for this example contract. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 private callbackGasLimit = 100_000;

    // The default is 3, but you can set this higher.
    // Cannot exceed VRFCoordinatorV2_5.MAX_REQUEST_CONFIRMATIONS.  [3, 200].
    uint16 private requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFCoordinatorV2_5.MAX_NUM_WORDS.  500
    uint32 private numWords = 2;

    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    // Collaborators can be added or removed by the owner.
    // Collaborators can request random words and receive the results.
    mapping(address => bool) public s_collaborators;

    constructor(uint256 subscriptionId, address coordinatorAddr, bytes32 keyhash)
        VRFConsumerBaseV2Plus(coordinatorAddr)
    {
        s_subscriptionId = subscriptionId;
        s_coordinatorAddr = coordinatorAddr;
        s_keyhash = keyhash;
        COORDINATOR = IVRFCoordinatorV2Plus(s_coordinatorAddr);
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords(uint32 _numWords, bool _nativePayment)
        external
        onlyOwnerOrCollaborators
        returns (uint256 requestId)
    {
        // Will revert if subscription is not set and funded.
        // To enable payment in native tokens, set nativePayment to true.
        if (_numWords > 500) {
            // "Number of words requested is out of range. Cannot exceed 500."
            revert VRFConsumerV2_5__OutofRange();
        }
        requestId = COORDINATOR.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: s_keyhash,
                subId: s_subscriptionId,
                requestConfirmations: requestConfirmations,
                callbackGasLimit: callbackGasLimit,
                numWords: _numWords,
                extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: _nativePayment}))
            })
        );
        s_requests[requestId] = RequestStatus({randomWords: new uint256[](0), exists: true, fulfilled: false});
        requestIds.push(requestId);
        lastRequestId = requestId;
        numWords = _numWords;
        emit RequestSent(requestId, _numWords);
        return requestId;
    }

    function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
        if (!s_requests[_requestId].exists) {
            revert VRFConsumerV2_5__NoRequestFound();
        }
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }

    function getRequestStatus(uint256 _requestId)
        external
        view
        returns (bool fulfilled, uint256[] memory randomWords)
    {
        if (!s_requests[_requestId].exists) {
            revert VRFConsumerV2_5__NoRequestFound();
        }
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }

    /* Functions */
    function setRequestConfirmations(uint16 _requestConfirmations) external onlyOwnerOrCollaborators {
        requestConfirmations = _requestConfirmations;
    }

    function setCallbackGasLimit(uint32 _callbackGasLimit) external onlyOwnerOrCollaborators {
        callbackGasLimit = _callbackGasLimit;
    }

    function getRequestConfirmations() external view returns (uint16) {
        return requestConfirmations;
    }

    function getCallbackGasLimit() external view returns (uint32) {
        return callbackGasLimit;
    }

    function getLastRequestNumWords() external view returns (uint32) {
        return numWords;
    }

    /* Collaborators */

    function addCollaborator(address _collaborator) external onlyOwnerOrCollaborators {
        if (s_collaborators[_collaborator]) {
            revert VRFConsumerV2_5__CollaboratorExists();
        }
        s_collaborators[_collaborator] = true;
        emit CollaboratorAdded(_collaborator);
    }

    function addCollaborators(address[] memory _collaborators) external onlyOwnerOrCollaborators {
        for (uint256 i = 0; i < _collaborators.length; i++) {
            address collaborator = _collaborators[i];
            // require(!s_collaborators[collaborator], "Collaborator already exists");
            if (s_collaborators[collaborator]) {
                continue;
            }
            s_collaborators[collaborator] = true;
            emit CollaboratorAdded(collaborator);
        }
    }

    function removeCollaborator(address _collaborator) external onlyOwnerOrCollaborators {
        if (!s_collaborators[_collaborator]) {
            revert VRFConsumerV2_5__CollaboratorNotExists();
        }

        delete s_collaborators[_collaborator];
        emit CollaboratorRemoved(_collaborator);
    }

    function removeCollaborators(address[] memory _collaborators) external onlyOwnerOrCollaborators {
        for (uint256 i = 0; i < _collaborators.length; i++) {
            address collaborator = _collaborators[i];
            // require(!s_collaborators[collaborator], "Collaborator already exists");
            if (!s_collaborators[collaborator]) {
                continue;
            }
            delete s_collaborators[collaborator];
            emit CollaboratorRemoved(collaborator);
        }
    }
}

ABI

[
 {
  "inputs": [],
  "name": "acceptOwnership",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "_collaborator",
    "type": "address"
   }
  ],
  "name": "addCollaborator",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address[]",
    "name": "_collaborators",
    "type": "address[]"
   }
  ],
  "name": "addCollaborators",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "uint256",
    "name": "subscriptionId",
    "type": "uint256"
   },
   {
    "internalType": "address",
    "name": "coordinatorAddr",
    "type": "address"
   },
   {
    "internalType": "bytes32",
    "name": "keyhash",
    "type": "bytes32"
   }
  ],
  "stateMutability": "nonpayable",
  "type": "constructor"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "have",
    "type": "address"
   },
   {
    "internalType": "address",
    "name": "want",
    "type": "address"
   }
  ],
  "name": "OnlyCoordinatorCanFulfill",
  "type": "error"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "have",
    "type": "address"
   },
   {
    "internalType": "address",
    "name": "owner",
    "type": "address"
   },
   {
    "internalType": "address",
    "name": "coordinator",
    "type": "address"
   }
  ],
  "name": "OnlyOwnerOrCoordinator",
  "type": "error"
 },
 {
  "inputs": [],
  "name": "VRFConsumerV2_5__CollaboratorExists",
  "type": "error"
 },
 {
  "inputs": [],
  "name": "VRFConsumerV2_5__CollaboratorNotExists",
  "type": "error"
 },
 {
  "inputs": [],
  "name": "VRFConsumerV2_5__NoRequestFound",
  "type": "error"
 },
 {
  "inputs": [],
  "name": "VRFConsumerV2_5__OutofRange",
  "type": "error"
 },
 {
  "inputs": [],
  "name": "ZeroAddress",
  "type": "error"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "address",
    "name": "collaborator",
    "type": "address"
   }
  ],
  "name": "CollaboratorAdded",
  "type": "event"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "address",
    "name": "collaborator",
    "type": "address"
   }
  ],
  "name": "CollaboratorRemoved",
  "type": "event"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": false,
    "internalType": "address",
    "name": "vrfCoordinator",
    "type": "address"
   }
  ],
  "name": "CoordinatorSet",
  "type": "event"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "address",
    "name": "from",
    "type": "address"
   },
   {
    "indexed": true,
    "internalType": "address",
    "name": "to",
    "type": "address"
   }
  ],
  "name": "OwnershipTransferRequested",
  "type": "event"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "address",
    "name": "from",
    "type": "address"
   },
   {
    "indexed": true,
    "internalType": "address",
    "name": "to",
    "type": "address"
   }
  ],
  "name": "OwnershipTransferred",
  "type": "event"
 },
 {
  "inputs": [
   {
    "internalType": "uint256",
    "name": "requestId",
    "type": "uint256"
   },
   {
    "internalType": "uint256[]",
    "name": "randomWords",
    "type": "uint256[]"
   }
  ],
  "name": "rawFulfillRandomWords",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "_collaborator",
    "type": "address"
   }
  ],
  "name": "removeCollaborator",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address[]",
    "name": "_collaborators",
    "type": "address[]"
   }
  ],
  "name": "removeCollaborators",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "uint256",
    "name": "requestId",
    "type": "uint256"
   },
   {
    "indexed": false,
    "internalType": "uint256[]",
    "name": "randomWords",
    "type": "uint256[]"
   }
  ],
  "name": "RequestFulfilled",
  "type": "event"
 },
 {
  "inputs": [
   {
    "internalType": "uint32",
    "name": "_numWords",
    "type": "uint32"
   },
   {
    "internalType": "bool",
    "name": "_nativePayment",
    "type": "bool"
   }
  ],
  "name": "requestRandomWords",
  "outputs": [
   {
    "internalType": "uint256",
    "name": "requestId",
    "type": "uint256"
   }
  ],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "anonymous": false,
  "inputs": [
   {
    "indexed": true,
    "internalType": "uint256",
    "name": "requestId",
    "type": "uint256"
   },
   {
    "indexed": false,
    "internalType": "uint32",
    "name": "numWords",
    "type": "uint32"
   }
  ],
  "name": "RequestSent",
  "type": "event"
 },
 {
  "inputs": [
   {
    "internalType": "uint32",
    "name": "_callbackGasLimit",
    "type": "uint32"
   }
  ],
  "name": "setCallbackGasLimit",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "_vrfCoordinator",
    "type": "address"
   }
  ],
  "name": "setCoordinator",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "uint16",
    "name": "_requestConfirmations",
    "type": "uint16"
   }
  ],
  "name": "setRequestConfirmations",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "to",
    "type": "address"
   }
  ],
  "name": "transferOwnership",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "getCallbackGasLimit",
  "outputs": [
   {
    "internalType": "uint32",
    "name": "",
    "type": "uint32"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "getLastRequestNumWords",
  "outputs": [
   {
    "internalType": "uint32",
    "name": "",
    "type": "uint32"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "getRequestConfirmations",
  "outputs": [
   {
    "internalType": "uint16",
    "name": "",
    "type": "uint16"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "uint256",
    "name": "_requestId",
    "type": "uint256"
   }
  ],
  "name": "getRequestStatus",
  "outputs": [
   {
    "internalType": "bool",
    "name": "fulfilled",
    "type": "bool"
   },
   {
    "internalType": "uint256[]",
    "name": "randomWords",
    "type": "uint256[]"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "lastRequestId",
  "outputs": [
   {
    "internalType": "uint256",
    "name": "",
    "type": "uint256"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "owner",
  "outputs": [
   {
    "internalType": "address",
    "name": "",
    "type": "address"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "uint256",
    "name": "",
    "type": "uint256"
   }
  ],
  "name": "requestIds",
  "outputs": [
   {
    "internalType": "uint256",
    "name": "",
    "type": "uint256"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "address",
    "name": "",
    "type": "address"
   }
  ],
  "name": "s_collaborators",
  "outputs": [
   {
    "internalType": "bool",
    "name": "",
    "type": "bool"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [
   {
    "internalType": "uint256",
    "name": "",
    "type": "uint256"
   }
  ],
  "name": "s_requests",
  "outputs": [
   {
    "internalType": "bool",
    "name": "fulfilled",
    "type": "bool"
   },
   {
    "internalType": "bool",
    "name": "exists",
    "type": "bool"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 },
 {
  "inputs": [],
  "name": "s_vrfCoordinator",
  "outputs": [
   {
    "internalType": "contract IVRFCoordinatorV2Plus",
    "name": "",
    "type": "address"
   }
  ],
  "stateMutability": "view",
  "type": "function"
 }
]