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 :
Inject web3, connect your Metamask (Rinkeby testnet) account to deploy the contract
You deploy the contract and the contract creates a subscription for you in the constructor using COORDINATOR.createSubscription(); where COOORDINATOR is VRFCoordinatorV2Interface
Your Metamask account -- owns --> deployed contract
Your deployed contract -- owns --> created subscription
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
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.
- 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 :
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)
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/
- Link you Metamask wallet to the website
- Create a subscription account using you Metamask (Rinkeby Testnet) account
- 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.