1

I've been learning solidity with brownie framework and python. I'm trying to write a very simple contract which will generate a random number using ChainLink VRF v2.

Here's the link of their sample Subscription Manager Contract: https://docs.chain.link/docs/chainlink-vrf/example-contracts/

I've copied this exact same code in my VRFv2SubscriptionManager.sol file :

// SPDX-License-Identifier: MIT
// An example of a consumer contract that also owns and manages the subscription
pragma solidity ^0.8.7;

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

contract VRFv2SubscriptionManager is VRFConsumerBaseV2 {
    VRFCoordinatorV2Interface COORDINATOR;
    LinkTokenInterface LINKTOKEN;

    // Rinkeby coordinator. For other networks,
    // see https://docs.chain.link/docs/vrf-contracts/#configurations
    address vrfCoordinator = 0x6168499c0cFfCaCD319c818142124B7A15E857ab;

    // Rinkeby LINK token contract. For other networks, see
    // https://docs.chain.link/docs/vrf-contracts/#configurations
    address link_token_contract = 0x01BE23585060835E02B77ef475b0Cc51aA1e0709;

    // 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 =
        0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc;

    // A reasonable default is 100000, but this value could be different
    // on other networks.
    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 = 2;

    // Storage parameters
    uint256[] public s_randomWords;
    uint256 public s_requestId;
    uint64 public s_subscriptionId;
    address public s_owner;

    constructor() VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        LINKTOKEN = LinkTokenInterface(link_token_contract);
        s_owner = msg.sender;
        //Create a new subscription when you deploy the contract.
        createNewSubscription();
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords() external onlyOwner {
        // 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 {
        s_randomWords = randomWords;
    }

    // Create a new subscription when the contract is initially deployed.
    function createNewSubscription() private onlyOwner {
        // Create a subscription with a new subscription ID.
        address[] memory consumers = new address[](1);
        consumers[0] = address(this);
        s_subscriptionId = COORDINATOR.createSubscription();
        // Add this contract as a consumer of its own subscription.
        COORDINATOR.addConsumer(s_subscriptionId, consumers[0]);
    }

    // Assumes this contract owns link.
    // 1000000000000000000 = 1 LINK
    function topUpSubscription(uint256 amount) external onlyOwner {
        LINKTOKEN.transferAndCall(
            address(COORDINATOR),
            amount,
            abi.encode(s_subscriptionId)
        );
    }

    function addConsumer(address consumerAddress) external onlyOwner {
        // Add a consumer contract to the subscription.
        COORDINATOR.addConsumer(s_subscriptionId, consumerAddress);
    }

    function removeConsumer(address consumerAddress) external onlyOwner {
        // Remove a consumer contract from the subscription.
        COORDINATOR.removeConsumer(s_subscriptionId, consumerAddress);
    }

    function cancelSubscription(address receivingWallet) external onlyOwner {
        // Cancel the subscription and send the remaining LINK to a wallet address.
        COORDINATOR.cancelSubscription(s_subscriptionId, receivingWallet);
        s_subscriptionId = 0;
    }

    // Transfer this contract's funds to an address.
    // 1000000000000000000 = 1 LINK
    function withdraw(uint256 amount, address to) external onlyOwner {
        LINKTOKEN.transfer(to, amount);
    }

    modifier onlyOwner() {
        require(msg.sender == s_owner);
        _;
    }
}

This is my deploy_first.py :

from brownie import VRFv2SubscriptionManager
from scripts.helpful_scripts import get_account
import time


def deploy_random_number():
    account = get_account()
    random_number = VRFv2SubscriptionManager.deploy({"from": account})

    print("Deployed Random Number!!!")

    owner = random_number.s_owner()
    print("owner : ", owner)

    sub_id = random_number.s_subscriptionId()
    print("sub_id : ", sub_id)

    random_number.topUpSubscription(9000000000000000000)
    print("after the top up")
    random_number.requestRandomWords()
    # time.sleep(60)
    print("array : ", random_number.s_randomWords())

    return random_number


def main():
    deploy_random_number()

I'm running this contract on Rinkeby TestNet and the account through which I'm running this contract, has enough ETHER and LINK because I've tried chainlink's official guide to generate the random number on REMIX and I was able to generate it.

Here's the link of remix implementation of code : https://remix.ethereum.org/#url=https://docs.chain.link/samples/VRF/VRFv2SubscriptionManager.sol

The Process on Remix :

The new release of chainlink VRF v2 works this way from what I've understood by implementing on REMIX :

  1. Inject web3, connect your Metamask (Rinkeby testnet) account to deploy the contract

  2. You deploy the contract and the contract creates a subscription for you in the constructor using COORDINATOR.createSubscription(); where COOORDINATOR is VRFCoordinatorV2Interface

  3. Your Metamask account -- owns --> deployed contract

  4. Your deployed contract -- owns --> created subscription

  5. Consumers under a subscription account are allowed to use LINKs of that account and we are assigning our contract as the first consumer in the constructor

  6. To make calls to oracle our subscription account pays in LINKs, to send my subscription account LINKs I transferred LINKs from my metamask account to the address of the deployed contract, then used the function :

     // Assumes this contract owns link.
     // 1000000000000000000 = 1 LINK
     function topUpSubscription(uint256 amount) external onlyOwner {
         LINKTOKEN.transferAndCall(
             address(COORDINATOR),
             amount,
             abi.encode(s_subscriptionId)
         );
     }
    

to send LINKs from my contract to the subscription account.

  1. This worked completely fine in REMIX and I was able to generate the random number

The Problem :

Funding subscription and contracts is easy with Metamask wallet and Remix but not scalable so I've got 2 main problems :

  1. How do I transfer LINKs from my Metamask (Rinkeby testnet) account (basically the first msg.sender i.e. owner of the contract) to the contract that has been in deployed using my VRFv2SubscriptionManager.sol and python_first.py? Is there a way to do this using web3.py? (basically automate the process of funding the subscription account with LINKs while deploying the contract and creating subscription in the code only)

  2. In this example the deployed contract -- owns --> subscription account after deploying the contract and creating a subscription using following functions :

    constructor() VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        LINKTOKEN = LinkTokenInterface(link_token_contract);
        s_owner = msg.sender;
        //Create a new subscription when you deploy the contract.
        createNewSubscription();
    }
    
    // Create a new subscription when the contract is initially deployed.
    function createNewSubscription() private onlyOwner {
        // Create a subscription with a new subscription ID.
        address[] memory consumers = new address[](1);
        consumers[0] = address(this);
        s_subscriptionId = COORDINATOR.createSubscription();
        // Add this contract as a consumer of its own subscription.
        COORDINATOR.addConsumer(s_subscriptionId, consumers[0]);
    }
    

Is there any way to create subscription, programatically, so that msg.sender i.e. my Metamask (Rinkeby Testnet) account is the owner of subscription account so that I don't have to transfer the LINKs to contract first and can directly send it to the subscription account? On browser you can do it easily here : https://vrf.chain.link/

  1. Link you Metamask wallet to the website
  2. Create a subscription account using you Metamask (Rinkeby Testnet) account
  3. Fund it with LINKs directly and in consumers you can add your deployed previously contracts using their addresses for them to use the LINKs to interact with VRF

I've tried changing the Gas Limit pre set in the contract, doublechecked my values that I'm sending but I'm getting following error when python compiler reaches the line : random_number.topUpSubscription(9000000000000000000) in my python_first.py

error :

Deployed Random Number!!!
owner :  0xD8154fBD7cf816CdFde8cBC397b7cF5C604d7154
sub_id :  4091
  File "brownie/_cli/run.py", line 51, in main
    return_value, frame = run(
  File "brownie/project/scripts.py", line 103, in run
    return_value = f_locals[method_name](*args, **kwargs)
  File "./scripts/deploy_first.py", line 28, in main
    deploy_random_number()
  File "./scripts/deploy_first.py", line 18, in deploy_random_number
    random_number.topUpSubscription(9000000000000000000)
  File "brownie/network/contract.py", line 1710, in __call__
    return self.transact(*args)
  File "brownie/network/contract.py", line 1583, in transact
    return tx["from"].transfer(
  File "brownie/network/account.py", line 644, in transfer
    receipt, exc = self._make_transaction(
  File "brownie/network/account.py", line 727, in _make_transaction
    raise VirtualMachineError(e) from None
  File "brownie/exceptions.py", line 93, in __init__
    raise ValueError(str(exc)) from None
ValueError: Gas estimation failed: 'execution reverted'. This transaction will likely revert. If you wish to broadcast, you must set the gas limit manually.

I assume this is because my contract doesn't have any LINK or ETHER i don't know, I need more clarity on this error too.

I've been stuck on this problem for a day now. Please help. Thanks in advance.

Tushar
  • 11
  • 1

1 Answers1

0

As you said, the easiest way is to go to the vrf.chain.link, connect your wallet, and manage your subscription(s). The initial flow is:

  • connect your wallet and select the proper network
  • create a new subscription, save your subscription id
  • fund your subscription
  • deploy VRF consumer smart contract
  • add it as a subscription consumer

The Subscription Manager page allows us to easily manage subscription funds, add/remove multiple consumer contracts to/from one subscription, and so much more.

Since you want to automate this process from the brownie CLI in a way that your smart contract is not the owner of a subscription (and doesn't need to worry about the funds), I suggest you move "subscription logic" from the smart contract to python scripts, as described in Chainlink Brownie Starter Kit.

  1. To transfer LINKs from your Metamask (Rinkeby testnet) account to the contract that has been deployed, just perform the regular ERC20 transfer, but add your contract's address as the receiver/destination address. More examples here and here.
  2. Probably because of that, try to set gas manually, as described in previous URLs, to some high value to confirm
Andrej
  • 534
  • 2
  • 7
  • 1
    Hey Andrej, thank you so much for replying. I'm definitely going to try the solution you mentioned, go through the links and revert back. Thanks again :) – Tushar May 10 '22 at 08:37
  • 1
    Hey Andrej, I went through the chainlink-mix documentation and shifted the logic to create the subscription inside python script and it worked. Thank you so much :) – Tushar May 18 '22 at 06:21
  • Great to hear that! My pleasure, sir :) – Andrej May 18 '22 at 20:36