Aladdin and the King of Randomness (Chainlink VRF)
There once lived a poor tailor, who had a smart contract called Aladdin, a careless, idle program that dreamt of a safely generated random number ...
Hello, fren,
Sorry for being away lately. I have been actively participating in various hackathons, applying for jobs, boot camps, and more. This is what I like about web3 and the IT sector as a whole - you are the master of your circumstances buddy, YOU. No one else. And It is guaranteed that eventually, your efforts will be noticed by someone. And after that, your life will change forever!
Anyways, today's TED talk will cover my experience with Chainlink and their VRF (Verifiable Random Function). I will explain what Chainlink is, what VRF is, why you need it and how to integrate it into your code.
But what even is Chainlink, ser?
Apologies beforehand, $LINK marines. But I need to do it. There are people who still don't know about one of the most significant blockchain inventions that your army obeys. So, for the people hearing about Chainlink for the first time, here it is: The simplest explanation of Chainlink is that it represents a network of oracles ( think of nodes ), which communicate information from the outside world(off-chain) to the smart contracts (on-chain). This information may come in many forms - price feeds, weather data, the outcome of sports events, etc.
Yooo, wait a minute?! We know that info on the blockchain is "isolated" from the info off-chain. Then how does this oracle thing pass the information to my smart contract?
Excluding the ingenious cryptography and verification processes that ser Nazarov has proposed for his product behind the scenes, this same question was what bothered me the most. And you will learn its answer in around 8 Ethereum blocks time (a nerdy equivalent to circa a minute and a half, if you keep reading at the same pace.)
Part of the task that a prospective recruiter gave me consisted of creating a lottery feature that would pick one random winner, who will get an award. And how do you safely generate a random number on the blockchain? Well, you don't. Simply because it may look random, but it will never be entirely random.
It is important to point out that always getting a different result does not mean this result is random.
See, the thing is that in most cases, developers who generate randomness on-chain use variables like msg.sender, block.timestamp, that private bytes32 hash of their favorite Mortal Kombat character's name, and more. But with some Sherlock Holmness, these variables or hashes could be figured out. You can learn more about how to unveil them by completing the first few challenges of Capture the Ether.
What Chainlink proposes as a solution to this is their VRF (Verifiable Random Function). This leaves the entire creation of random numbers to these oracle guys, which I tackled some lines above.
The randomness generation process is 2-step:
- Our contract "asks" a Chainlink oracle to generate a random number for it. This happens when you call a special function in your contract, inherited from the Chainlink contract that is used as a "helper". The function is called requestRandomWords() (stay calm fellow fren, just remember the "words" word in the function's name is simply a synonym for the word "number").
- The oracle generates this random number off-chain and then sends it back to our contract, together with a cryptographic proof that the number is indeed random and NOT created by Frank Abagnale from Catch me if you can (2002). The function via which the oracle hands over the randomly generated number to the contract is called fulfillRandomWords().
And, would you be surprised to learn that VRF is actually an ancient piece of technology? It existed back during Aladdin's time.
"Hey, Genie-Chainlink oracle, remember that you owe me a wish? I am here to request a random number from you. I want it ASAP." said Aladdin, the smart contract.
"Hey, Aladin, I will be more than happy to fulfill your requestRandomWords() wish. Dope rug btw. Reminds me of the same 2021 crypto rugs that got me broke. Kek. Anyways. Once I'm ready, how should I send you the random number?" said, the Genie-Chainlink oracle.
"You can DM it to me on my AladdinGram, my handle is "@ fulfillRandomWords"." said Aladdin, the smart contract.
"... Blogger's degen brain checks the time elapsed in unix and realizes that the 8 blocks he talked about have already been created.."
And here is the best place to summarize and answer the question about how the oracle sends the information back to the contract:
By invoking another function, also part of this same contract. Eureka!
Let's now see what we need to do in order to be able to ask the oracles for their almighty power.
NOTICE: In case you encounter any problem during the steps, feel free to take a look at my repository and fork it or just copy/paste.
- We need some Goerli ETH and test $LINK tokens, because for the example, we will use the Goerli Testnet.
- We need to create a subscription over at Chainlink's VRF Subscription Management dashboard. Think of this as the box from your childhood, full of money for the month ahead. The one from which your parents asked you to take the money and go get some groceries.
- You need to make your contract "enlightened". In other words, make it inherit the
VRFConsumerBaseV2
contract by Chainlink which will allow it to ask the oracles for random numbers and receive them back. - Add the already deployed contract as a consumer back in the VRF Subscription Management dashboard.
I. Getting some $LINK.
This is ez like a 1v5 CS:GO ace on Cache. Go to Chainlink's Goerli Faucet and make sure to have checkmarks on both '20 test LINK' and '0.1 test ETH' in the Request type section.
II.Create a subscription.
Even ez-er. Go to Chainlink's VRF homepage. Click 'Create Subscription'. Once you are in, you should see a page like the one on the image:
Here, we will create the actual subscription. You should input the wallet address from which you are going to deploy the contract. After doing that, the system will create a Subscription ID, which we will use as an input for our contract's constructor when we deploy it. By doing that, we will establish a connection between it and the subscription that we just created.
After clicking 'Create Subscription', an option to add funds to the subscription should pop up. You could safely put the 10 LINK which we got from the faucet.
The last step of the subscription creation is to actually add the contract that we are going to deploy as a "consumer" of these $LINK tokens and Chainlink's services. So we will come back to this in a minute.
III. Making our contract "enlightened".
Now comes the time to create our contract and add the secret sauce that makes it talk to the oracles.
a/ Click here to import the VRFV2Consumer.sol
contract straight into Remix. I would suggest using this contract and building up on top of it. But you can also integrate the same functionality in any contract that needs a random number:
import these two contracts:
import '@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol';
import '@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol';
and inherit the VRFConsumerBaseV2
:
contract Marketplace is VRFConsumerBaseV2
This is where things get interesting. You would notice two functions in the VRFV2Consumer.sol
contract:
function requestRandomWords() external onlyOwner returns (uint256 requestId)
/*and*/
function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override
These two functions explain the entire communication between our contract and the Chainlink oracles. When we call the first function, we send a request via the requestRandomWords() to the oracle to generate a random number for us. This function will give us an "id" which will correspond to the request for a random number that we just made. Think of this as waiting for your Starbucks order to get prepared. You are given a ticket. Then the barista (oracle) calls our order number and we get our drink. The only difference in our case is that rather than calling our "name", the oracle will call another function from the contract. Yes, the fulfillRandomWords() one. Once it is ready with generating the number, it will send it to our contract by calling this function (the oracle can call it because the function is with external visibility, in case you wonder) and will store the number and the requestId in a mapping, which also resides in our VRFV2Consumer.sol
contract. Then we can use this random number and do pretty much whatever we want with it. in the example below, I use it to randomly select a winner and send him an award in the form of an NFT:
function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
require(s_requests[_requestId].exists, 'request not found');
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWords = _randomWords; // this line stores together the generated random number and the requested id that we got when we called the requestRandomWord() function
// and from here until the end of the function body,
//we can do whatever we want with the _randomWords variable (which holds the generated random number that we received from the oracle ).
//As I said above, in my example, I invoke a mint function and pass the _randomWords as an argument to it:
eventContracts[s_eventContractId].mintToWinner((_randomwords % 10000)*1);
// the % 10000 part basically converts the _randomWords number to one between 0 and 10000 (because 10000 is a common NFT collection size)
emit RequestFulfilled(_requestId, _randomWords); // an event that broadcasts that the random number generation process has concluded successfully.
}
b/ Now that we have the logic ready, the last part of this process is the deployment of the contract. Head over to the "Deploy and Run Transactions" tab on Remix. It is important to select Injected Provider in the Environment section. Also, make sure that your Metamask is connected to the Goerli Testnet. Then pass the subscription ID that we created earlier as an argument to the contract's constructor.
And you have just created a verifiable source of randomness. One small step for mankind, one giant leap for the self-taught developer! Amazing job!
I'm wrapping it up in a min. Let's just do a quick summary of what we learned today:
Chainlink is a network of oracles ( think of nodes ), which communicate information from the outside world(off-chain) to the smart contracts (on-chain). This information may come in many forms - price feeds, weather data, the outcome of sports events, etc.
The Verifiable Random Function (VRF) is a service, that Chainlink offers as a cryptographically provable way to generate randomness off-chain and use it on-chain.
The smart contract communicates with the Chainlink network of oracles via functions in its code. One function is used to send a request to the oracle, and another is called by the oracle once it is ready to send back the random number that it generated.
A simple tutorial on how to integrate VRF into your code and make use of the randomly generated number.
And a final fun fact to make you laugh: Prior to realizing that the VRF function is a two-step process (request and receive), I used only the requestId that I received after calling requestRandomWrods() because I got misled by the fact that the ID is always different (hence, I thought it was random).LOL!. Thankfully, I managed to fix this and commit a properly working code for the employer to review! What a journey!
WAGMI
(2021) YouTube. YouTube. Available at: youtube.com/watch?v=JqZWariqh5s (Accessed: October 1st, 2022).
Introduction to chainlink VRF: Chainlink documentation (no date) Chainlink Developers. Available at: docs.chain.link/docs/vrf/v2/introduction (Accessed: October 1st, 2022).