How to Schedule Randomness – DZone Web Dev

From roulette wheels to action-adventure games, randomness plays a major role in the gaming world. This is for any scenario where opponents or scenes are created dynamically and for those where users need randomness “on the fly”.

This is a far cry from my memories of the 1980s, when gaming was far less enjoyable, because everything was predictable—if your character went on a mission once, the next time you play the mission Absolutely Same. As you can imagine, it got boring very quickly.

However, randomness has many other uses in the web3 ecosystem such as:

The DAO and Public Partnerships Processes: one of the following greatest web3 Features have existence of communities driving projects. In the long term, I think the projects that will be successful will be those with strong and active communities. In this case, randomness is needed to ensure fairness in the reward and participation processes.

NFT Generation: Over the past 5 years we have seen exponential growth of NFTs and digital arts. In some cases, NFT collections have random features that affect the value.

Lottery: Beyond traditional lottery games, the DeFi ecosystem allows innovations such as “no-loss” lotteries.

Due to the nature of the blockchain, where miners can create/discard blocks, and “re-roll” the dice until a specific value is found, no manipulable randomness can be created on-chain.

In all cases but especially in lotteries and NFT generation where large amounts of values ​​exist, it is imperative to use randomness that cannot be manipulated.


current solution

Most of the current solutions are using Oracle off-chain. We should request numbers, which (once available) will enable us to execute our custom logic. In this article we will find out:

  • Don’t know

  • API3 QRNG

  • Chainlink VRF

In this article, I will show Here’s how to implement the three solutions in conjunction with Gelato.


automation and randomness

The need for self-executing methods within smart contracts is increasing every day. However the “Big Bang” will happen when blockchain technology expands to non-blockchain business.

For example, all processes (no matter the industry) are under quality control which Relies on random sampling to drive controls To ensure that no bias exists.

Due to the specifics of getting random numbers into the blockchain via Oracle, two methods are needed:

  • To request a random number to an Oracle

  • The second is a callback when the number is available. Chainlink VRF and API 3 QRNG execute the callback code in your contract, however, Witnet does not.

Gelato comes in handy to deal with this situation. By creating a task, it will check when a random number is available and execute any custom logic, This task will be canceled after one run

In our Showcase project, we will demonstrate this use case for Witnet, Chainlink, and API3.


show case project

Let’s imagine for a moment that we run an automobile factory, and our main assembly machine has 20 components. Although we do periodic maintenance (another example of automation), we need to run random quality controls every 10 minutes. Here’s how it would look:

  1. Choosing 2 components, each component can be controlled once per hour.

  2. Choose the level of control: Express, Medium or Intense

  3. Selecting one of 500 employees to run quality control

To run tamper-proof quality control and avoid bias, our system must generate random results for all steps.

The repo can be found here.

we will use Gelato as a Master of Ceremony Orchestrate to run all tasks, such as calling Oracle, checking if results are available, and updating component arrays. e.t.c. Dapp deployed on Goerly https://gelato-vrf.web.app . is live on

The major contract “Schedule the Randomness” can be found here. Etherscan verified log here:
https://goerli.etherscan.io/address/0x6a0C105A74Ed3359ADDd877049BC129e224b48c0#code


Step 1) Gelato as the Master of Ceremony

We will use gelato to drive the plan, every 15 minutes we will do quality control. To do this, we need:

a) Wire up the Gelato infrastructure, first we need to copy IOPS.solAnd OpsReady.solBoth files can be found here

b) After copying the files, we can insert import and get contract:

import {OpsReady}  from "./gelato/OpsReady.sol"; 
import {IOps}  from "./gelato/IOps.sol"; 

contract ScheduleTheRandomness is  OpsReady   constructor( address payable ops) 
OpsReady(ops) {  IOps(ops).createTimedTask(,,,); 
}

c) Create a Timed Task:

function createQualityPlanTask() public {
        bytes32 taskId = IOps(ops).createTimedTask(
        0,
       900,      
       address(this),      
       this.doQualityControl.selector,      
       address(this),       
       abi.encodeWithSelector(this.checkQualityPlanIsActive.selector),       
       ETH,       
       false);    
       qualityPlanTaskId = taskId; 
}

This task will check if 15 minutes have passed and if any old controls are still running checkQualityPlanIsActive() and then run doQualityControl() method where the control flow starts with various calls to request random numbers.

A very interesting use of gelato is to execute custom logic once random numbers are available. In the case of Witnet we are obliged to do this as Witnet does not provide callbacks, but we can also implement it in Chainlink or API 3 to have more control over the callback execution.

we can use Gelato for making requests Randomness Oracles as well to Control callback execution

step 2) API3 QRNG for random components

In this step, we will use API3 QRNG To generate 2 random components to be controlled by API3.

a) We are required to wire API3 infrastructure in our contract which means that:- It is necessary to add npm i @api3/airnode-protocol And import “@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol"; in your contract.

– We have to inherit the contract and pass the airnode address for the respective chain in the constructor, the addresses can be found here.

//  Goerli 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd 
contract ScheduleTheRandomness is RrpRequesterV0 {
		constructor(address _airnodeRrp) 
        RrpRequesterV0(_airnodeRrp){   
        } }

b) We need to fund the airnode to retrieve the QRNG. To do this, we have to create and fund a sponsor walletEither through API3 admin CLI or by getting sponsor wallet address from contract address:- npm i @api3/airnode-adminTo get the Sponsor Wallet – Call the derived Sponsor WalletAddress().

The xpub and airnode params can be found here in the API3 Providers section.

API3 QRNG Quick Recap: We have set up and wired up the infrastructureAnd Created (and funded) a sponsor wallet To pay for the transaction. Now we’ll set the parameters and related methods

c) setting parameters. We need to pass the following para in our contract

// we will endpointIdUint256 if we only want one random nuumber back // or endpointIdUint256Array if we want more than one random number // as result 
function setRequestParameters(    
		address _airnode, ///    
        bytes32 _endpointIdUint256Array,    
        address _sponsorWallet) external {       
         airnode = _airnode;       
         endpointIdUint256Array = _endpointIdUint256Array;       
         sponsorWallet = _sponsorWallet; 
        }

Both endpointIdUint256 And endpointIdUintArray256 Can also be found here in the API3 Providers section

d) In this last step we have to create two methods airnodeRrp.makeFullRequest() and callback after random delivered in our case fullfillRandomComponents() As defined in request.

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "hardhat/console.sol";
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";

contract ScheduleTheRandomness is   RrpRequesterV0 {

  // #region ====== API3 QRNG STATE ================
  event RequestedUint256Array(bytes32 indexed requestId, uint256 size);
  event ReceivedUint256Array(bytes32 indexed requestId, uint256[] response);

  mapping(bytes32 => bool) public expectingRequestWithIdToBeFulfilled;

  address public airnode;
  bytes32 public endpointIdUint256Array;
  address public sponsorWallet;

  uint256[] public qrngUint256Array;

  // #endregion ====== API3 QRNG STATE ================

  constructor(

    address _airnodeRrp,
    uint64 subscriptionId
  )  rpRequesterV0(_airnodeRrp)  { }

  // #region ========= STEP 2 GET RANDOM COMPONENTS WITH API3

  /// @notice Sets parameters used in requesting QRNG services
  /// @dev No access control is implemented here for convenience. This is not
  /// secure because it allows the contract to be pointed to an arbitrary
  /// Airnode. Normally, this function should only be callable by the "owner"
  /// or not exist in the first place.
  /// @param _airnode Airnode address
  /// @param _endpointIdUint256Array Endpoint ID used to request a `uint256[]`
  /// @param _sponsorWallet Sponsor wallet address
  function setRequestParameters(
    address _airnode,
    bytes32 _endpointIdUint256Array,
    address _sponsorWallet
  ) external {
    airnode = _airnode;
    endpointIdUint256Array = _endpointIdUint256Array;
    sponsorWallet = _sponsorWallet;
  }

  /// @notice Requests a `uint256[]`
  /// @param size Size of the requested array
  function makeRequestAPI3RandomComponents(uint256 size) public {
    qrngUint256Array = [0, 0];
    bytes32 requestId = airnodeRrp.makeFullRequest(
      airnode, //// airnode provider
      endpointIdUint256Array, /// type of 
      address(this),
      sponsorWallet,
      address(this),
      this.fulfillRandomComponents.selector,
      // Using Airnode ABI to encode the parameters
      abi.encode(bytes32("1u"), bytes32("size"), size)
    );
    //  uint256 requestId = 123;
    expectingRequestWithIdToBeFulfilled[requestId] = true;
  }

  /// @notice Called by the Airnode through the AirnodeRrp contract to
  /// fulfill the request
  /// @param requestId Request ID
  /// @param data ABI-encoded response
  function fulfillRandomComponents(bytes32 requestId, bytes calldata data)
    external
    onlyAirnodeRrp
  {
    require(
      expectingRequestWithIdToBeFulfilled[requestId],
      "Request ID not known"
    );
    expectingRequestWithIdToBeFulfilled[requestId] = false;
    qrngUint256Array = abi.decode(data, (uint256[]));
  }

  // #endregion STEP 2 GET RANDOM COMPONENTS WITH API3

 
}


Step 2: Witnet for Random Control Type

In this step, we will request a random number between 1 and 3 to represent the three different control types (Express, Medium, Intensive). To do this we will use **Randomness Agreement**t by Witnet oracle.

a) We are required to wire API3 infrastructure into our contract:npm i witnet-solidity-bridgeAnd import “witnet-solidity-bridge/contracts/interfaces/IWitnetRandomess.sol";,

b) To interact with the Randomness contract we must create an instance of it within our contract. First, we need to copy the chain address we are working on (contract address). In this particular case, we are on Goerly, so we will pass the randomness contract address to Goeerly by deployment. After that, within the constructor, we will create an instance of the Randomness contract

constructor(IWitnetRandomness _witnetRandomness){ witnet = _witnetRandomness; }

c) We will request a random number from Witnet

function getRandomControlType() public returns (uint8 _controlType) {
   latestRandomizingBlock = block.number;
   uint256 _usedFunds = witnet.randomize{ value: 0.1 ether }();
   if (taskIdByBlock[latestRandomizingBlock] == bytes32(0){
       createTaskQualityControl();
    }
}

d) Unlike API 3 and Chainlink VRF Witnet, Witnet does not provide callbacks, however, does provide randomness contractsisRandomized() , a method that specifies whether a random number can be retrieved. Once isRandomized() true, we say witnet.random(3,0,latestRandomizingBlok);

We’ll use Gelato to create an “on the fly” task, which checks when isRandomized() Returns true, and then will receive the random number and continue the control flow


//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

import "hardhat/console.sol";
import {OpsReady} from "./gelato/OpsReady.sol";
import {IOps} from "./gelato/IOps.sol";

import "witnet-solidity-bridge/contracts/interfaces/IWitnetRandomness.sol";

contract ScheduleTheRandomness is
  OpsReady,
  Ownable
{
  // #region ====== WITNET RANDOMNESS CONTRACT STATE ================

  uint32 public randomness;
  uint256 public latestRandomizingBlock;
  mapping(uint256 => bytes32) public taskIdByBlock;
  IWitnetRandomness witnet;
  // #endregion ====== WITNET RANDOMNESS CONTRACT STATE ================


  constructor(
    address payable _ops,
    IWitnetRandomness _witnetRandomness
  )
    OpsReady(_ops)
  {
    witnet = _witnetRandomness;

  }


  // #region ========= STEP 3 GET CONTROL TYPE WITH WITNET

  // Random numbers request
  function getRandomControlType() public returns (uint8 _controlType) {
    latestRandomizingBlock = block.number;
    uint256 _usedFunds = witnet.randomize{value: 0.1 ether}();

    if (taskIdByBlock[latestRandomizingBlock] == bytes32(0)) {
      createTaskQualityControl();
    }
  }

  // Gelato Task to check whether the random mumber is available under checkerIsRandomized()
  // and then execute qualityControlDelivered()
  function createTaskQualityControl() public {
    bytes32 taskId = IOps(ops).createTaskNoPrepayment(
      address(this),
      this.qualityControlDelivered.selector,
      address(this),
      abi.encodeWithSelector(this.checkerIsRandomized.selector),
      ETH
    );

    taskIdByBlock[latestRandomizingBlock] = taskId;
  }

  // Check if random number is delivered
  function checkerIsRandomized()
    public
    view
    returns (bool canExec, bytes memory execPayload)
  {
    canExec = isRandomnize();

    execPayload = abi.encodeWithSelector(this.qualityControlDelivered.selector);
  }

  function isRandomnize() public view returns (bool ready) {
    ready = witnet.isRandomized(latestRandomizingBlock);
  }

  // Cancel the task as we will require every time only one execution
  function cancelQualityTypeByID(bytes32 _taskId) public {
    IOps(ops).cancelTask(_taskId);
    taskIdByBlock[latestRandomizingBlock] = bytes32(0);
  }

  // Custome logic to be executed
  function qualityControlDelivered() external onlyOps {
    assert(latestRandomizingBlock > 0);
    randomness = 1 + witnet.random(3, 0, latestRandomizingBlock);

    uint256 fee;
    address feeToken;

    (fee, feeToken) = IOps(ops).getFeeDetails();

    _transfer(fee, feeToken);

    cancelQualityTypeByID(taskIdByBlock[latestRandomizingBlock]);
 
  }

  //  #endregion STEP 3 GET CONTROL TYPE WITH WITNET

}

In the next image, we can see both functions, the first running every 15 minutes for overall quality control and the second only active while we are waiting for Witnet random numbers.


Step 3: Chainlink VRF to Select Employee

Our final step is to select an employee to run the controls. To do this, we will use Chainlink VRF to get a random number between 1 and 500 (total number of employees).

a) Chainlink VRF requires us to create a funded subscription with the link which will then be used by our contract (consumer contract) to pay a fee for requesting random numbers. You can do that here

Once a subscription is created and funded, you can add your contract as a subscriber.

Once your contract is added to the subscription as a subscriber we can continue with the settings

b) We need to wire up Chainlink VRF Infrastructure in our contract which means:

,npm i @chainlink/contracts And

import “@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol”; 
import “@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol”

– Inherit the contract and pass the subscription ID and VRF coordinator address to the constructor for the respective chain. Addresses can be found here (in our case is Goerly)

// Goerli
address vrfCoordinator = address(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);

constructor(

    uint64 subscriptionId
  )
    VRFConsumerBaseV2(vrfCoordinator)
  {

    // Instance of the coordinator
    COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);


    // Subscription Id
    s_subscriptionId = subscriptionId;
  }

c) Now that our funding mechanism is in place and the contract infrastructure is wired, we should write our request and callback methods COORDINATOR.requestRandomWords() And fullfillRandomwords() , Chainlink VRF gives us the ability to set various parameters while calling requestRandomWords(), The code and params can be found here:


//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

import "hardhat/console.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";


contract ScheduleTheRandomness is
  VRFConsumerBaseV2
{

  // #region ======  CHAINLINK VRF STATE ================
  VRFCoordinatorV2Interface COORDINATOR;
  // Your subscription ID.
  uint64 s_subscriptionId;

  // Goerli coordinator. For other networks,
  // see https://docs.chain.link/docs/vrf-contracts/#configurations
  address vrfCoordinator = address(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);

  // 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-contracts/#configurations
  bytes32 keyHash =
    0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;

  // 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 callbackGasLimit = 100000;

  // 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 VRFCoordinatorV2.MAX_NUM_WORDS.
  uint32 numWords = 1;

  uint256 public employeeId;
  uint256 public s_requestId;
  address s_owner;

  // #endregion ======  CHAINLINK VRF STATE ================

  constructor(
    uint64 subscriptionId
  )
    VRFConsumerBaseV2(vrfCoordinator)
  {
    COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);

    s_owner = msg.sender;
    s_subscriptionId = subscriptionId;

  }

  // region =========  STEP 4 GET EMPLOYEEID with CHAINLINK VRF

  // Assumes the subscription is funded sufficiently.
  function requestEmployeByChainlink() public {
    // Will revert if subscription is not set and funded.
    s_requestId = COORDINATOR.requestRandomWords(
      keyHash,
      s_subscriptionId,
      requestConfirmations,
      callbackGasLimit,
      numWords
    );
  }

  function fulfillRandomWords(
    uint256, /* requestId */
    uint256[] memory randomWords
  ) internal override {
    employeeId = randomWords[0] & (500 + 1);
   
  }

  // endregion STEP 4 GET EMPLOYEEID with CHAINLINK VRF

}


conclusion

In my personal opinion, all the technologies mentioned in this article (Gelato Network, Chainlink VRF, Witnet, and API 3 QRNG) are incredibly user-friendly and easy to learn.

Step by step, the web3 community is solving the underlying problems of blockchain in a very efficient way. In the same way, Gelato Network solves the problem of scheduled tasksand three VRF Technologies The proposal analyzed here a Very efficient way to generate and consume random numbers on-chain.

Gelato networks can be used in a variety of ways when dealing with randomness, from calling a random number at a specific point, once the random number is distributed, to executing additional logic when other conditions are met. waiting to happen.

In our showcase project, for the sake of simplicity, we have randomized three in a row, but we can also parallelize the random call and create a task that checks if all three numbers are already available or execute custom logic. Huh.

Going forward We can see automation protocols like Gelato Networks as a perfect match for VRF generation solutions By providing a very elegant way to schedule random number requests and organize callbacks and execution logic.


Thanks for reading!!

If you have any questions or comments, please follow me on Twitter @donoso_eth. ping on


Resource

API3

API3

API3 QRNG

airnode addresses

API3 Provider

API3 Admin CLI

Don’t know

Witnet Oracle

randomness contract

Witnet contract addresses

Chainlink VRF

Chainlink VRF

chainlink faucet

VRF Coordinator Addresses

Ice Cream

Gelato Network

ops ui ice cream

Leave a Comment