Smart Contract Interaction With The Outside World: Chainlink vs Foundry.

Ifeoluwaolubo
Coinmonks
9 min readNov 11, 2023

--

Inasmuch as we want to make our applications as decentralized as possible in the sense that there are no “external forces” acting on it (for example a centralized organization trying to force the entire system in their favour), we soon realize that quite often, a lot of what we do in our smart contracts still has to interact with the outside world and in essence, centralized bodies.

You might be wondering though, what are the cases where our contracts would need this said interactions?
A lot of real world applications we end up building actually needs us to interact with the outside world. For example, your contract might need to convert some amount of ethers to USD in real time, or maybe you need some sort of automations to trigger some things in your contract, getting a random value, etc. All this requires external interactions with our contracts.

Randomization on the blockchain is a bit tricky to achieve, which is why we often require external tools to get random values.

Where does chainlink come in?

According to their official website at chain.link. Chainlink is the decentralized computing platform powering the verifiable web. All the problems listed above and more is what chainlink hopes to solve. So rather than relying on centralized organizations and trusting that they always behave accordingly, chainlink brings the interaction we need from the external world to a more decentralized ecosystem.

In this article, we’ll see how we can generate random numbers using chainlink VRF (verifiable randomness function) and we would be doing this with our newly found tool “Foundry”. Let’s get started 🚀.

Getting started

To follow along you can visit the chainlink documentation below

To get started, we’ll initialize a new foundry project with

forge init

Then we’ll delete all the dummy counter related contracts in the src, test and script folders. I created a ChainlinkDemo.sol contract in the src folder like below. I’ll be using VsCode for this article.

Chainlink VRF

The biggest thing to know about VRF is that you are funding a subscription which is like an account to maintain balance from multiple other contracts. To create your own subscription, head over to the random number generation section on the docs here and follow the steps on the “Create and fund subscription section”.

You need to have some test ETH and link on your chosen testnet. At the time of writing, the docs uses the Sepolia Testnet and that’s what we’ll stick with here. You can get some test ETH and link at faucets.chain.link.
After that, follow the processes involved in creating a subscription and fund it with ETH and link.

create subscription

After setting it all up, you should arrive at an add consumer page like below. Keep this open for later.

Getting a random number with VRF is a 2 transaction functions, the first requests the random number generator and the second gets the random number. When we scroll down the get a random number documentation, we’ll see a code section for the VRFv2Consumer contract and we’ll use this code to get our random number. In particular with are interested in the code block highlighted in green below.

So let’s copy this into our contract:

Of course we’ll get plenty errors, but we’ll sort them out soon. Let’s first understand what that code block is doing.

  • The COORDINATOR is the chainlink’s VRF coordinator address that we will be making the request to. This returns a requestId when we call the requestRandomWords function.
  • The keyHash is your gas lane. It specifies whether or not you want to spend too much gas. (Same with the callbackGasLimit).
  • The s_subscriptionId is the Id of the subscription we’ve funded with link when following the create subscription process from above.
  • The requestConfirmations is the number of blocks confirmations for our random number to be considered as good.
  • The numWords specifies how many random numbers we want to generate.

The coordinator address and keyHash is different for whichever chain we are using.

We created a constructor that accepts a couple of parameters and since they won’t be changing, we set them as either immutable or constant variables. After setting all our variables and passing them to the requestRandomWords function, we then get the error that our i_vrfCoordinator which is an address does not have the requestRandomWords function on it. For this to be resolved, we’ll have to import the VRFCoordinateV2Interface from chainlink like in the documentation.

Then the we need to run the install command below to pull what we need from chainlink.

forge install smartcontractkit/chainlink --no-commit

But the issue with installing this directly from chainlink’s repo is that I found it to be a really big pull (almost about 300mb). Alternatively, we could install a rather simplified version using.

forge install smartcontractkit/chainlink-brownie-contracts@0.6.1 --no-commit

I’ll be sticking with the first install command.

In our foundry.toml file, we write the following lines to make our installed package accessible and visible to our contract.

remappings = ['@chainlink/contracts/=lib/chainlink/contracts']

// if you're using the minified one, then use
remappings = ['@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts']

If everything was configured properly, then the error should not show up again.

We changed the type of our i_vrfCoordinator to now be the imported interface, then type cast the vrfCoordinator address passed as a parameter in our constructor also to the imported interface.

The next thing we need to do is also copy another code from the documentation which chainlink node is going to call to give us back our random number.

The override keyword there simply means that we want to override the fulfillRandomWords function that our contract is inheriting from, but since we are not doing any inheritance currently on our contract, we get the error as above. Let’s make our contract inherit from the VRFConsumerBaseV2 as in the documentation.

Notice how this gives us a bunch of errors? This is because we need to call the constructor of VRFConsumerBaseV2 we are inheriting from.

Whenever we are inheriting from a contract that has a constructor, we have to pass this constructor into our own constructor as well.

Notice how we passed the VRFConsumerBaseV2 constructor into our own constructor by calling it and passing the address of the vrfCoordinator. Now all the errors should go away. To keep track of the requests we have made to get a random number, we create the storage variables as seen below:

Then use it in our randomNumber function:

As stated earlier, the fulfillRandomWords is what would be called by the chainlink node, passing the requestId and the numbers that were generated. We then store the number in our s_requests variable and make a few updates.

Finally, we’ll create a few getter functions to get some values we might be interested in.

We can definitely test this locally using the tools that foundry provides, but just so we test very quickly, I’ll be using remix to test. So copy your code to remix, then in deploy and run transactions section, select injected provider — metamask, this is because we are going to be deploying to the SEPOLIA testnet as opposed to remix’s VM. Then fill out the parameters needed to deploy your contract like below.

The subscriptionId is the id of the subscription you created earlier. To get the vrfCoordinator address and the key hash for the testnet, in this case Sepolia, go to supported network section on the docs and you’ll see the different details for your testnet if supported.

Once the contract has deployed, copy the address of the deployed contract and add it as a consumer in the add consumer section from earlier.

Once you’ve added the consumer, go back to remix and interact with your deployed contract by first clicking on the randomNumber button, then click on the getLastRequestId button to get your requestId, then check that the request has been fulfilled by clicking on the getRequestStatus button. Once this returns true, then click on the getNumber button and this returns the random Number 😋.

You can find the full code below

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.21;

import {VRFCoordinatorV2Interface} from "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import {VRFConsumerBaseV2} from "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

contract ChainlinkDemo is VRFConsumerBaseV2 {
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;

VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
bytes32 private immutable i_keyHash;
uint64 private immutable i_subscriptionId;
uint32 private immutable i_callbackGasLimit;

struct RequestStatus {
bool fulfilled;
bool exists;
uint256[] randomWords;
}
mapping(uint256 => RequestStatus) private s_requests;
uint256[] private s_requestIds;
uint256 private s_lastRequestId;

constructor(
uint64 subscriptionId,
address vrfCoordinator,
bytes32 keyHash,
uint32 callbackGasLimit
) VRFConsumerBaseV2(vrfCoordinator) {
i_subscriptionId = subscriptionId;
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinator);
i_keyHash = keyHash;
i_callbackGasLimit = callbackGasLimit;
}

function randomNumber() external {
uint256 requestId = i_vrfCoordinator.requestRandomWords(
i_keyHash,
i_subscriptionId,
REQUEST_CONFIRMATIONS,
i_callbackGasLimit,
NUM_WORDS
);
s_requests[requestId] = RequestStatus({
fulfilled: false,
exists: true,
randomWords: new uint256[](0)
});
s_requestIds.push(requestId);
s_lastRequestId = requestId;
}

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;
}

function getLastRequestId() public view returns (uint256) {
return s_lastRequestId;
}

function getRequestStatus(uint256 requestId) public view returns (bool) {
return s_requests[requestId].fulfilled;
}

function getNumber(
uint256 requestId
) public view returns (uint256[] memory) {
return s_requests[requestId].randomWords;
}

function getRequestIds() public view returns (uint256[] memory) {
return s_requestIds;
}
}

Alright then, in this article we’ve talked about the fact that generating random numbers on the blockchain can be very tricky and we can leverage chainlink VRF to help us generate a random number following the steps outlined on the documentation. Please feel free to still dive into the docs to better understand what is going on and building upon what we have done in this article.

In the next article, we’ll also be looking at another of chainlink’s wonderful tool that helps us to get real time data feeds, specifically the real time equivalent of ethers to USD.

I hope you found this article useful, please give a clap, follow, and comment. I’ll see you in the next one. 🚀🤩

--

--

Ifeoluwaolubo
Coinmonks

A software developer with love for the blockchain