NEW

The Chainlink Hackathon kicks off April 28th! Register today to compete for $450K+ in prizes.

Local testing using a Mock contract

This guide explains how to test Chainlink VRF v2 on a Remix IDE sandbox blockchain environment. Note: You can reuse the same logic on another development environment, such as Hardhat or Truffle. For example, read the Hardhat Starter Kit RandomNumberDirectFundingConsumer unit tests.

Benefits of local testing

Testing locally using mock contracts saves you time and resources during development. Some of the key benefits include:

  • Faster feedback loop: Immediate feedback on the functionality and correctness of your smart contracts. This helps you quickly identify and fix issues without waiting for transactions to be mined/validated on a testnet.
  • Saving your native testnet tokens: Deploying and interacting with contracts requires paying gas fees. Although testnet native tokens do not have any value associated with them, their supply is limited by public faucets. Using mock contracts locally allows you to test your contracts freely without incurring any expenses.
  • Controlled environment: Local testing allows you to create a controlled environment where you can manipulate various parameters, such as block time and gas prices, to test your smart contracts’ function as expected under different conditions.
  • Isolated testing: You can focus on testing individual parts of your contract, ensuring they work as intended before integrating them with other components.
  • Easier debugging: Because local tests run on your machine, you have better control over the debugging process. You can set breakpoints, inspect variables, and step through your code to identify and fix issues.
  • Comprehensive test coverage: You can create test cases to cover all possible scenarios and edge cases.

Testing logic

Complete the following tasks to test your VRF v2 consumer locally:

  1. Deploy the VRFCoordinatorV2Mock. This contract is a mock of the VRFCoordinatorV2 contract.
  2. Deploy the MockV3Aggregator contract.
  3. Deploy the LinkToken contract.
  4. Deploy the VRFV2Wrapper contract.
  5. Call the VRFV2Wrapper setConfig function to set wrapper specific parameters.
  6. Fund the VRFv2Wrapper subscription.
  7. Call the the VRFCoordinatorV2Mock addConsumer function to add the wrapper contract to your subscription.
  8. Deploy your VRF consumer contract.
  9. Fund your consumer contract with LINK tokens.
  10. Request random words from your consumer contract.
  11. Call the VRFCoordinatorV2Mock fulfillRandomWords function to fulfill your consumer contract request.

Prerequisites

This guide will require you to finetune the gas limit when fulfilling requests. When writing, manually setting up the gas limits on RemixIDE is not supported, so you will use RemixIDE in conjunction with Metamask. Ganache lets you quickly fire up a personal Ethereum blockchain. If you still need to install Ganache, follow the official guide.

  1. Once Ganache is installed, click the QUICKSTART button to start a local Ethereum node.

    Note: Make sure to note the RPC server. In this example, the RPC server is http://127.0.0.1:7545/.

  2. Follow the Metamask official guide to add a custom network manually.

  3. Import a Ganache account into Metamask.

    1. On Ganache, click on the key symbol of the first account:

    2. Copy the private key:

    3. Follow the Metamask official guide to import an account using a private key.

    4. Your Metamask is connected to Ganache, and you should have access to the newly imported account.

Testing

Open the contracts on RemixIDE

Open VRFCoordinatorV2Mock and compile in Remix:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/mocks/VRFCoordinatorV2Mock.sol";

Open MockV3Aggregator and compile in Remix:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/tests/MockV3Aggregator.sol";

Open LinkToken and compile in Remix:

// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24;

import "@chainlink/contracts/src/v0.4/LinkToken.sol";

Open VRFV2Wrapper and compile in Remix:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/VRFV2Wrapper.sol";

Open RandomNumberDirectFundingConsumerV2 and compile in Remix:

// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
import "@chainlink/contracts/src/v0.8/VRFV2WrapperConsumerBase.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UNAUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract RandomNumberDirectFundingConsumerV2 is
    VRFV2WrapperConsumerBase,
    ConfirmedOwner
{
    event RequestSent(uint256 requestId, uint32 numWords, uint256 paid);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );
    error InsufficientFunds(uint256 balance, uint256 paid);
    error RequestNotFound(uint256 requestId);
    error LinkTransferError(address sender, address receiver, uint256 amount);

    struct RequestStatus {
        uint256 paid; // amount paid in link
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus)
        public s_requests; /* requestId --> requestStatus */

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

    // configuration: https://docs.chain.link/vrf/v2/direct-funding/supported-networks#configurations
    constructor(
        address _linkAddress,
        address _wrapperAddress
    )
        ConfirmedOwner(msg.sender)
        VRFV2WrapperConsumerBase(_linkAddress, _wrapperAddress)
    {}

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. 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.
    // The default is 3, but you can set this higher.
    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
    function requestRandomWords(
        uint32 _callbackGasLimit,
        uint16 _requestConfirmations,
        uint32 _numWords
    ) external onlyOwner returns (uint256 requestId) {
        requestId = requestRandomness(
            _callbackGasLimit,
            _requestConfirmations,
            _numWords
        );
        uint256 paid = VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit);
        uint256 balance = LINK.balanceOf(address(this));
        if (balance < paid) revert InsufficientFunds(balance, paid);
        s_requests[requestId] = RequestStatus({
            paid: paid,
            randomWords: new uint256[](0),
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, _numWords, paid);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        RequestStatus storage request = s_requests[_requestId];
        if (request.paid == 0) revert RequestNotFound(_requestId);
        request.fulfilled = true;
        request.randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords, request.paid);
    }

    function getNumberOfRequests() external view returns (uint256) {
        return requestIds.length;
    }

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

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink(address _receiver) public onlyOwner {
        bool success = LINK.transfer(_receiver, LINK.balanceOf(address(this)));
        if (!success)
            revert LinkTransferError(
                msg.sender,
                _receiver,
                LINK.balanceOf(address(this))
            );
    }
}

Your RemixIDE file explorer should display the opened contracts:

Select the correct RemixIDE environment

Under DEPLOY & RUN TRANSACTIONS:

  1. Set the Environment to Injected Provider - Metamask:

  2. On Metamask, connect your Ganache account to the Remix IDE.

  3. Click on Connect. The RemixIDE environment should be set to the correct environment, and the account should be the Ganache account.

Deploy VRFCoordinatorV2Mock

  1. Open VRFCoordinatorV2Mock.sol.

  2. Under DEPLOY & RUN TRANSACTIONS, select VRFCoordinatorV2Mock.

  3. Under DEPLOY, fill in the _BASEFEE and _GASPRICELINK. These variables are used in the VRFCoordinatorV2Mock contract to represent the base fee and the gas price (in LINK tokens) for the VRF requests. You can set: _BASEFEE=100000000000000000 and _GASPRICELINK=1000000000.

  4. Click on transact to deploy the VRFCoordinatorV2Mock contract.

  5. A Metamask popup will open. Click on Confirm.

  6. Once deployed, you should see the VRFCoordinatorV2Mock contract under Deployed Contracts.

  7. Note the address of the deployed contract.

Deploy MockV3Aggregator

The MockV3Aggregator contract is designed for testing purposes, allowing you to simulate an oracle price feed without interacting with the existing Chainlink network.

  1. Open MockV3Aggregator.sol.

  2. Under DEPLOY & RUN TRANSACTIONS, select MockV3Aggregator.

  3. Under DEPLOY, fill in _DECIMALS and _INITIALANSWER. These variables are used in the MockV3Aggregator contract to represent the number of decimals the aggregator’s answer should have and the most recent price feed answer. You can set: _DECIMALS=18 and _INITIALANSWER=3000000000000000 (We are considering that 1 LINK = 0.003 native tokens).

  4. Click on transact to deploy the MockV3Aggregator contract.

  5. A Metamask popup will open. Click on Confirm.

  6. Once deployed, you should see the MockV3Aggregator contract under Deployed Contracts.

  7. Note the address of the deployed contract.

Deploy LinkToken

The Chainlink VRF v2 direct funding method requires your consumer contract to pay for VRF requests in LINK. Therefore, you have to deploy the LinkToken contract to your local blockchain.

  1. Open LinkToken.sol.

  2. Under DEPLOY & RUN TRANSACTIONS, select LinkToken.

  3. Under DEPLOY, click on transact to deploy the LinkToken contract.

  4. A Metamask popup will open. Click on Confirm.

  5. Once deployed, you should see the LinkToken contract under Deployed Contracts.

  6. Note the address of the deployed contract.

Deploy VRFV2Wrapper

As the VRF v2 direct funding end-to-end diagram explains, the VRFV2Wrapper acts as a wrapper for the coordinator contract.

  1. Open VRFV2Wrapper.sol.

  2. Under DEPLOY & RUN TRANSACTIONS, select VRFV2Wrapper.

  3. Under DEPLOY, fill in _LINK with the LinkToken contract address, _LINKETHFEED with the MockV3Aggregator contract address, and _COORDINATOR with the VRFCoordinatorV2Mock contract address.

  4. click on transact to deploy the VRFV2Wrapper contract.

  5. A Metamask popup will open. Click on Confirm.

  6. Once deployed, you should see the VRFV2Wrapper contract under Deployed Contracts.

  7. Note the address of the deployed contract.

Configure the VRFV2Wrapper

  1. Under Deployed Contracts, open the functions list of your deployed VRFV2Wrapper contract.

  2. Click on setConfig and fill in _wrapperGasOverhead with 60000, _coordinatorGasOverhead with 52000, _wrapperPremiumPercentage with 10, _keyHash with 0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc, and _maxNumWords with 10. Note on these variables:

    1. _wrapperGasOverhead: This variable reflects the gas overhead of the wrapper’s fulfillRandomWords function. The cost for this gas is passed to the user.

    2. _coordinatorGasOverhead: This variable reflects the gas overhead of the coordinator’s fulfillRandomWords function. The cost for this gas is billed to the VRFV2Wrapper subscription and must, therefore, be included in the VRF v2 direct funding requests pricing.

    3. _wrapperPremiumPercentage: This variable is the premium ratio in percentage. For example, a value of 0 indicates no premium. A value of 15 indicates a 15 percent premium.

    4. _keyHash: The gas lane key hash value is the maximum gas price you are willing to pay for a request in wei.

    5. _maxNumWords: This variable is the maximum number of words requested in a VRF v2 direct funding request.

  3. click on transact.

  4. A Metamask popup will open. Click on Confirm.

Fund the VRFV2Wrapper subscription

When deployed, the VRFV2Wrapper contract creates a new subscription and adds itself to the newly created subscription. If you started this guide from scratch, the subscription ID should be 1.

  1. Under Deployed Contracts, open the functions list of your deployed VRFCoordinatorV2Mock contract.

  2. Click fundSubscription to fund the VRFV2Wrapper subscription. In this example, you can set the _subid to 1 (which is your newly created subscription ID) and the _amount to 10000000000000000000 (10 LINK).

  3. A Metamask popup will open. Click on Confirm.

Deploy the VRF consumer contract

  1. In the file explorer, open RandomNumberDirectFundingConsumerV2.sol.

  2. Under DEPLOY & RUN TRANSACTIONS, select RandomNumberDirectFundingConsumerV2.

  3. Under DEPLOY, fill in _LINKADDRESS_ with the LinkToken contract address, and _WRAPPERADDRESS_ with the deployed VRFV2Wrapper address.

  4. Click on transact to deploy the RandomNumberDirectFundingConsumerV2 contract.

  5. A Metamask popup will open. Click on Confirm.

  6. Once deployed, you should see the RandomNumberDirectFundingConsumerV2 contract under Deployed Contracts.

  7. Note the address of the deployed contract.

Fund your VRF consumer contract

  1. Under Deployed Contracts, open the functions list of your deployed LinkToken contract.

  2. Click on transfer and fill in the _to with your consumer contract address and _value with LINK tokens amount. For this example, you can set the _value to 10000000000000000000 (10 LINK).

  3. Click on transact.

  4. A Metamask popup will open. Click on Confirm.

Request random words

Request three random words.

  1. Under Deployed Contracts, open the functions list of your deployed RandomNumberConsumerV2 contract.

  2. In requestRandomWords, fill in _callbackGasLimit with 300000, _requestConfirmations with 3 and _numWords with 3.

  3. Click on transact.

  4. A Metamask popup will open.

  5. Click on Confirm.

  6. In the RemixIDE console, read your transaction logs to find the VRF request ID. In this example, the request ID is 1.

  7. Note your request ID.

Fulfill the VRF request

Because you are testing on a local blockchain environment, you must fulfill the VRF request yourself.

  1. Under Deployed Contracts, open the functions list of your deployed VRFCoordinatorV2Mock contract.

  2. Click fulfillRandomWords and fill in _requestId with your VRF request ID and _consumer with the VRFV2Wrapper contract address.

  3. Click on transact.

  4. A Metamask popup will open.

  5. Click on Confirm.

  6. In the RemixIDE console, read your transaction logs to find the random words.

Check the results

  1. Under Deployed Contracts, open the functions list of your deployed RandomNumberDirectFundingConsumerV2 contract.

  2. Click on lastRequestId to display the last request ID. In this example, the output is 1.

  3. Click on getRequestStatus with _requestId equal to 1:

  4. You will get the amount paid, the status, and the random words.

    Next steps

This guide demonstrated how to test a VRF v2 consumer contract on your local blockchain. We made the guide on RemixIDE for learning purposes, but you can reuse the same testing logic on another development environment, such as Truffle or Hardhat. For example, read the Hardhat Starter Kit RandomNumberDirectFundingConsumer unit tests.

Stay updated on the latest Chainlink news