# WINkLink Random Number Service

# Overview

Verifiable Random Function (VRF) is the public-key version of a keyed cryptographic hash, which can be used as a random number. Only the holder of the private key can compute the hash, but anyone with the public key can verify the correctness of the hash. VRF can be used to generate secure and reliable random numbers.

Random number is determined by seed (provided by users), nonce (private state of VRFCoordinator contract) , block hash (the block of the request event) and private key of oracle node.

The generation process of VRF is:

  • A Dapp contract sends out an on-chain request for a random number;
  • Once the off-chain oracle node listens for the request, it generates a random number attaching the cryptographic proof to make the generated random number verifiable, and then submits them back to an oracle contract (VRFCoordinator);
  • Once the random number proof is verified by the oracle contract, the random number is published to the Dapp contract through a callback function.

The process above ensures that the random number cannot be tampered with nor manipulated by anyone, including oracle operators, miners, users and even smart contract developers.

WINkLink VRF is a provably-fair and verifiable source of randomness designed for Dapp contracts. Dapp contract developers can use WINkLink VRF as a tamper-proof RNG (Random Number Generator) to build reliable smart contracts for any applications which rely on unpredictable random number:

  • Blockchain games and NFTs
  • Random assignment of duties and resources (e.g. randomly assigning judges to cases)
  • Choosing a representative sample for consensus mechanisms

The WINkLink VRF solution contains both off-chain and on-chain components:

  • VRF Coordinator (on-chain component): Crafted to interact with the VRF service. It emits an event when a request for randomness is initiated, and then validates the resultant random number and proof of how it was generated by the VRF service.
  • VRF Wrapper (on-chain component): A wrapper for the VRF Coordinator that offers an interface for consuming contracts.
  • VRF service (off-chain node): Listens for requests by subscribing to the VRF Coordinator event logs and calculates a random number based on the block hash and nonce. The VRF service then sends a transaction to the VRFCoordinator including the random number and a proof of how it was generated.

This article describes how to deploy and use the WINkLink VRF service.

# VRF Request Process

  1. The Dapp contract invokes the calculateRequestPrice function of VRFV2Wrapper to estimate the total transaction cost required for random number generation.

  2. The Dapp contract calls the transferAndCall function of WinkMid to pay the calculated request price to the Wrapper. This method sends Wink tokens and executes the onTokenTransfer logic of VRFV2Wrapper.

  3. The onTokenTransfer logic of VRFV2Wrapper triggers the requestRandomWords function of VRFCoordinatorV2 to request a random number.

  4. The VRFCoordinatorV2 contract emits the RandomWordsRequested event.

  5. The VRF node captures this event and awaits the specified number of block confirmations. It then returns the random values and proof through the fulfillRandomWords function to the VRFCoordinatorV2 contract.

  6. The VRFCoordinatorV2 contract verifies the proof on the blockchain and subsequently calls back the fulfillRandomWords function of VRFV2Wrapper.

  7. Lastly, the VRFV2Wrapper calls back the Dapp contract to complete the request.

# Before you start

Maintainers for WINkLink need to understand how the TRON platform works, and know about smart contract deployment and the process of calling them. You're suggested to read related TRON official documents (opens new window), particularly those on contract deployment on TronIDE.

Prepare the node account. You should read related Node account preparation doc (opens new window).

# VRFCoordinatorV2 Contract

VRFCoordinatorV2 contract is deployed on the TRON public chain with the following features:

  • Receive random number requests from Dapp contract and emit VRFRequest event
    • WIN transfer as fees, will be sent along with the request
  • Accept random number and the proof submitted from WINkLink node
    • VRFCoordinator contract will verify the proof before sending the random number to Dapp contract
  • Calculate the WINkLink node rewards for the request fulfilment

Some parameters are needed in the constructor function when deploying a VRFCoordinator contract:

  constructor(
    address wink,
    address blockhashStore,
    address winkMid
  )

_blockHashStore BlockHashStore address, _win WIN token address, _winkMid WinkMid contract address.

# VRFV2Wrapper Contract

VRFV2Wrapper streamlining the interaction and allowing direct calls from the Dapp to the VRFCoordinatorV2 contract.

Configuration parameters
keyHash : Node keyhash
maxNumWords : maximum number of random words per vrf request,currently set as 10

# Authorize a Node Account

Node account needs authorization to submit data to VRFCoordinatorV2 contract, otherwise error will be reported.

The owner of the VRFCoordinatorV2 contract is required to call the contract below and add the node account to the whitelist:

  function registerProvingKey(address oracle, uint256[2] calldata publicProvingKey) external onlyOwner

_oracle is the address of the registered node, which is used to receive the WIN token paid by DAPP , _publicProvingKey is the public key used by the registration node to generate random numbers,

Call example: registerProvingKey(TYmwSFuFuiDZCtYsRFKCNr25byeqHH7Esb,['6273228386041830135141271310112248407537170435188969735053134748570771583756',67273502359025519559461602732298865784327759914690240925031700564257821594585'])

# Dapp Contract

Main steps to set up your consuming contract:

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

import "./VRFV2WrapperConsumerBase.sol";

contract VRFv2DirectFundingConsumer is VRFV2WrapperConsumerBase{}
  • b) Contract must implement the fulfillRandomWords function, which is the callback VRF function. Here, you add logic to handle the random values after they are returned to your contract.
 function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    )
  • c) Contract invoke requestRandomness function to trigger a VRF request.
 function requestRandomWords()
    external
    onlyOwner
    returns (uint256 requestId)
    {
        requestId = requestRandomness(
            msg.sender,
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit, numWords),
            randomWords: new uint256[](0),
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

Dapp Contract Sample

Deploy sample consumer contract VRFv2DirectFundingConsumer.sol。

Constructor parameters:
_winkAddress:wink token contract address
_winkMid: winkMid contract address
_wrapper:VRFV2Wrapper contract address
_numWords: number of random words per vrf request\

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

import "./ConfirmedOwner.sol";
import "./VRFV2WrapperConsumerBase.sol";

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

contract VRFv2DirectFundingConsumer is
VRFV2WrapperConsumerBase,
        ConfirmedOwner
{
  address winkAddress;

  event RequestSent(uint256 requestId, uint32 numWords);
  event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
);

  struct RequestStatus {
  uint256 paid; // amount paid in wink
  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;

  // 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.
  uint32 callbackGasLimit = 0;

  // The default is 3, but you can set this higher.
  uint16 requestConfirmations = 3;

  // For this example, retrieve 2 random values in one request.
  // Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
  uint32 numWords;

  constructor(
          address _winkAddress,
        address _winkMid,
        address _wrapper,
        uint32 _numWords
)
  ConfirmedOwner(msg.sender)
  VRFV2WrapperConsumerBase(_winkMid, _wrapper) {
  winkAddress = _winkAddress;
  numWords = _numWords;
}

  function requestRandomWords()
  external
  onlyOwner
  returns (uint256 requestId)
  {
    requestId = requestRandomness(
            msg.sender,
            callbackGasLimit,
            requestConfirmations,
            numWords
    );
    s_requests[requestId] = RequestStatus({
      paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit, numWords),
      randomWords: new uint256[](0),
      fulfilled: false
    });
    requestIds.push(requestId);
    lastRequestId = requestId;
    emit RequestSent(requestId, numWords);
    return requestId;
  }

  function fulfillRandomWords(
          uint256 _requestId,
        uint256[] memory _randomWords
) internal override {
  require(s_requests[_requestId].paid > 0, "request not found");
  s_requests[_requestId].fulfilled = true;
  s_requests[_requestId].randomWords = _randomWords;
  emit RequestFulfilled(
          _requestId,
          _randomWords,
          s_requests[_requestId].paid
  );
}

  function getRequestStatus(
          uint256 _requestId
)
  external
  view
  returns (uint256 paid, bool fulfilled, uint256[] memory randomWords)
  {
    require(s_requests[_requestId].paid > 0, "request not found");
    RequestStatus memory request = s_requests[_requestId];
    return (request.paid, request.fulfilled, request.randomWords);
  }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ConfirmedOwnerWithProposal.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./OwnableInterface.sol";

/**
 * @title The ConfirmedOwner contract
 * @notice A contract with helpers for basic contract ownership.
 */
contract ConfirmedOwnerWithProposal is OwnableInterface {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /**
   * @notice Allows an owner to begin transferring ownership to a new address,
   * pending.
   */
  function transferOwnership(address to) public override onlyOwner {
    _transferOwnership(to);
  }

  /**
   * @notice Allows an ownership transfer to be completed by the recipient.
   */
  function acceptOwnership() external override {
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /**
   * @notice Get the current owner
   */
  function owner() public view override returns (address) {
    return s_owner;
  }

  /**
   * @notice validate, transfer ownership, and emit relevant events
   */
  function _transferOwnership(address to) private {
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /**
   * @notice validate access
   */
  function _validateOwnership() internal view {
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /**
   * @notice Reverts if called by anyone other than the contract owner.
   */
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface OwnableInterface {
  function owner() external returns (address);

  function transferOwnership(address recipient) external;

  function acceptOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./TRC20Interface.sol";
import "./VRFV2WrapperInterface.sol";

/** *******************************************************************************
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2 requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2 subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2WrapperConsumerBase. The consumer must be funded
 * @dev with enough WINK to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomness' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2WrapperConsumerBase {
  WinkMid internal immutable WINK_MID;
  VRFV2WrapperInterface internal immutable VRF_V2_WRAPPER;

  /**
   * @param _winkMid is the address of WinkMid
   * @param _vrfV2Wrapper is the address of the VRFV2Wrapper contract
   */
  constructor(address _winkMid, address _vrfV2Wrapper) {
    WINK_MID = WinkMid(_winkMid);
    VRF_V2_WRAPPER = VRFV2WrapperInterface(_vrfV2Wrapper);
  }

  /**
   * @dev Requests randomness from the VRF V2 wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2 request ID of the newly created randomness request.
   */
  function requestRandomness(
          address _from,
          uint32 _callbackGasLimit,
          uint16 _requestConfirmations,
          uint32 _numWords
) internal returns (uint256 requestId) {
    WINK_MID.transferAndCall(
            _from,
            address(VRF_V2_WRAPPER),
            VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit, _numWords),
            abi.encode(_callbackGasLimit, _requestConfirmations, _numWords)
    );
    return VRF_V2_WRAPPER.lastRequestId();
  }

  /**
   * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
  function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

  function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
    require(msg.sender == address(VRF_V2_WRAPPER), "only VRF V2 wrapper can fulfill");
    fulfillRandomWords(_requestId, _randomWords);
  }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface VRFV2WrapperInterface {
  /**
   * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
  function lastRequestId() external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   */
  function calculateRequestPrice(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint64);

  //   /**
  //   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
  //   *
  //   * @dev This is a convenience function that can be called in simulation to better understand
  //   * @dev pricing.
  //   *
  //   * @param _callbackGasLimit is the gas limit used to estimate the price.
  //   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
  //   */
  //   function estimateRequestPrice(uint32 _callbackGasLimit, uint256 _requestGasPriceWei) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract TRC20Interface {

  function totalSupply() public view virtual returns (uint);

  function balanceOf(address guy) public view virtual returns (uint);

  function allowance(address src, address guy) public view virtual returns (uint);

  function approve(address guy, uint wad) public virtual returns (bool);

  function transfer(address dst, uint wad) public virtual returns (bool);

  function transferFrom(address src, address dst, uint wad) public virtual returns (bool);

  event Transfer(address indexed from, address indexed to, uint tokens);
  event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

abstract contract WinkMid {

  function setToken(address tokenAddress) public virtual;

  function transferAndCall(address from, address to, uint64 tokens, bytes calldata _data) public virtual returns (bool success);

  function balanceOf(address guy) public view virtual returns (uint);

  function transferFrom(address src, address dst, uint wad) public virtual returns (bool);

  function allowance(address src, address guy) public view virtual returns (uint);

}

# Tron Nile VRF Contracts

For convenience, Nile testnet has deployed WinkMid contract and encapsulated the WIN token on it. Developers can use this contract address directly without additional deployment. Users can also claim test TRX and WIN tokens from the Faucet address provided by Nile testnet.

Item Value
WIN Token TNDSHKGBmgRx9mDYA9CnxPx55nu672yQw2
WinkMid TJpkay8rJXUWhvS2uL5AmMwFspQdHCX1rw
BlockHashStore TBpTbK9KQzagrN7eMKFr5QM2pgZf6FN7KA
VRFCoordinatorV2 TMvQdnsahiJRiJpA7YgcpLUdKFi2LswPrb
VRFV2Wrapper TJSP3zzmEH84y2W8hjfTgpqwhQsdWwn3N5
Fee 10 WIN

Testnet Faucet: https://nileex.io/join/getJoinPage (opens new window)

# Tron Mainnet VRF Contracts

Item Value
WIN Token TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7
WinkMid TSG1B8DKDGY5sRFXwQ6xJofVr75DCFUA64
BlockHashStore TRGmef4qUdNJ4xTEL96hToGuMTNst57aS1
VRFCoordinatorV2 TD7hF84Xwf8Cu2zscmqxrgiGaEBziZhXqf
VRFV2Wrapper TYMSMoitSkxuKUF1oiZp2fse4MEgsM86WT
Fee 10 WIN