Creating a Lottery with Hardhat and Chainlink
Tags: blockchain, solidity, hardhat, ethereum
Published: 2022-01-04 16:11:55


>The code repo is [here](https://github.com/johburn/crypto-lottery). In this tutorial, I will be covering only the smart contract. But the repo also has a frontend powered by Vite (React), Ethers.js, and Tailwind CSS to interact with the smart contract, and a simple backend because I wanted to keep the data off-chain, so I'm using WebSockets to listen to the contract events to sync data into a Postgres database.

A couple of months ago, I started my journey into web3 by learning Solidity and haven't stopped experimenting with it since then. One of the topics that caught my attention very early in this stage was randomness.

Randomness generation requires a source of [entropy](https://en.wikipedia.org/wiki/Entropy_(computing)), which is typically related to hardware-related events such as keypress, free memory, mouse movements, or unpredictable physical variables. In Solidity, you could rely on the block variables as the source of entropy, but you need to be careful because miners can manipulate those values.

Fortunately, oracles are here to help us. These are entities that connect the blockchain with external data. The oracle will help us get the random number from outside the network in this tutorial. Some of the most established oracle solutions are [Chainlink](https://chain.link/) and [Provable](https://provable.xyz/) (Formerly Oraclize).

I think you already have an idea which one we will be using. Right?

Chainlink provides different alternatives to get off-chain data. To solve the random generation problem, it offers a Verifiable Random Function (VRF) to obtain random numbers from outside the blockchain.

### What we will build

We will be building a lottery smart contract with the following features: 

- It will have an admin.
- The admin will be the only one who will create lotteries.
- A lottery will have a ticket price, an end date, and a single winner.
- Players will be able to participate by paying the ticket price.
- Players will be able to participate any number of times.
- Each participation increases the likelihood of winning.
- The winner will be declared after the end of the lottery by taking a random address from the participation list.

### Prerequisites

1. [Nodejs](https://nodejs.org) installed on your machine.
2. An [Achemy](https://www.alchemy.com/) or [Infura](https://infura.io/) account. (I will be using Alchemy)

### Setup

We will start by creating a folder and initializing the project.

```sh
mkdir cryptoLottery
cd cryptoLottery
npm init -y
```

Next, install hardhat using yarn or npm.

```sh
yarn add hardhat --dev
```
Now, we will initialize hardhat. I will be using the advanced project because it installs some linters that I found helpful, but feel free to use the simple project to avoid unnecessary package installation.

```sh
npx hardhat

888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

Welcome to Hardhat v2.8.0

? What do you want to do? … 
  Create a basic sample project
▸ Create an advanced sample project
  Create an advanced sample project that uses TypeScript
  Create an empty hardhat.config.js
  Quit

```
Next, we will install [hardhat-deploy](https://github.com/wighawag/hardhat-deploy) to improve our deployment experience and [fund-link](https://github.com/appliedblockchain/chainlink-consumer/tree/master/plugins/fund-link) plugin to help us fund our contract with LINK. Hardhat-deploy will allow us to deploy on different networks and keep track of them. Also, it helps to re-use our deployments during tests.

```sh
yarn add hardhat-deploy @appliedblockchain/chainlink-plugins-fund-link --dev
```

Now, change your `hardhat.config.js` to look like this:

```js
require("dotenv").config();
require("@nomiclabs/hardhat-etherscan");
require("@nomiclabs/hardhat-waffle");
// Advance project aditional libraries
require("hardhat-gas-reporter");
require("solidity-coverage");
//////////////////////////////////////
require("hardhat-deploy");
require("@appliedblockchain/chainlink-plugins-fund-link");

module.exports = {
  networks: {
    rinkeby: {
      url: process.env.RINKEBY_URL || "",
      chainId: 4,
      accounts: [
        process.env.PRIVATE_KEY_DEPLOYER,
        process.env.PRIVATE_KEY_USER_2,
        process.env.PRIVATE_KEY_USER_3,
      ].filter((x) => x !== undefined),
    },
  },
  gasReporter: {
    enabled: process.env.REPORT_GAS !== undefined,
    currency: "USD",
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
  namedAccounts: {
    deployer: {
      default: 0,
      4: 0,
    },
    user2: {
      default: 1,
      4: 1,
    },
    user3: {
      default: 2,
      4: 2,
    },
  },
  solidity: {
    compilers: [
      {
        version: "0.8.7",
      },
      {
        version: "0.6.6",
      },
      {
        version: "0.4.24",
      },
    ],
  },
  mocha: {
    timeout: 10000000,
  },
};
```
Some of the changes to this file:

- Plugins `hardhat-deploy` and `@appliedblockchain/chainlink-plugins-fund-link` imported at the beginning of the file.
- Rinkeby network configured in the networks list.
- NamedAccounts is a feature from hardhat-deploy that allows us to name the accounts.
- Additional compiler versions configured. This is required to compile the contracts we will use from Chainlink.

Finally, create a `.env` file with the following values:

```props
RINKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/<key>
PRIVATE_KEY_DEPLOYER=<private_key_1>
PRIVATE_KEY_USER_2=<private_key_2>
PRIVATE_KEY_USER_3=<private_key_3>
```

### Coding the Contract

Hardhat sample project generates some files we won't use, so let's delete them.

```sh
rm contracts/Greeter.sol
rm scripts/deploy.js
rm test/sample-test.js
```

Next, we create our smart contract in the contracts folder.

```sh
touch contracts/Lottery.sol
```

```solidity
pragma solidity ^0.8.0;

contract LotteryGame {

}
```

### Chainlink VRF prerequisites

Before getting into the details of our contract, these are the prerequisites we need to meet for Chainlink VRF to work.

- Our contract should inherit `VRFBaseConsumer` from the Chainlink contracts package.
- Call `requestRandomness` in our contract to request the random number.
- Define the `fulfillRandomness` function, which is the function that will receive the random number and do something with it.
- Define the following parameters in our contract:
 - **LINK Token:** Link token address on the network you are deploying your contract to. 
 - **VRF Coordinator address:** address of the VRF Coordinator on the network you are deploying your contract to.
 - **Key Hash:** public key used to generate randomness.
 - **Fee:** LINK fee required on the network to fulfill the VRF request. 

> **Warning:** without LINK in our contract we won't be able to request a random number.

We will adapt our contract to meet all these requirements later.

### Libraries

Besides using Chainlink, we will also use some libraries from Openzeppelin:

`Counters` to obtain an incremental integer that will be used as an id for our lotteries.
`SafeMath` to avoid overflow in math operations.

Let's install both of them:

```sh
yarn add @chainlink/contracts @openzeppelin/contracts
```
And import them into our contract.

```solidity
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
```
Our contract must inherit from `VRFConsumerBase`. This new contract will provide us with methods to request the random number and to receive the result.

```solidity
contract LotteryGame is VRFConsumerBase {

using Counters for Counters.Counter;
using SafeMath for uint256;

....
}
```
#### Structs

Next, we define our Lottery struct according to the definitions from above:

```solidity
struct Lottery {
    uint256 lotteryId;
    address[] participants;
    uint256 ticketPrice;
    uint256 prize;
    address winner;
    bool isFinished;
    uint256 endDate;
}
```
Then, we add some variables to help us with lotteries management.

```solidity
Counters.Counter private lotteryId;
mapping(uint256 => Lottery) private lotteries;
mapping(bytes32 => uint256) private lotteryRandomnessRequest;
mapping(uint256 => mapping(address => uint256)) ppplayer; //participations per player
mapping(uint256 => uint256) playersCount;
bytes32 private keyHash;
uint256 private fee;
address private admin;
```
▸ `lotteryId` will be an incremental uint we will use to identify our lotteries.
▸ `lotteries` will store all of our lotteries.
▸ `lotteryRandomnessRequest` will let us know how the request for a random number and a lottery are related. More on this later.
▸ `ppplayer` will store the number of times an address have participated in a lottery.
▸ `playersCount` will keep the number of unique addresses participating in a lottery.
▸ `keyHash` and `fee` are parameters required by Chainlink VRF.
▸ `admin` will be the deployer's address and will be the only address capable of creating lotteries and declaring winners.

### Events
We also define some events:
```solidity
event RandomnessRequested(bytes32,uint256);
event WinnerDeclared(bytes32,uint256,address);
event PrizeIncreased(uint256,uint256);
event LotteryCreated(uint256,uint256,uint256,uint256);
```
### Functions
Following VRF prerequisites, our contract constructor will take all the VRF required parameters as arguments and supply the first two to the VRFConsumerBase constructor. Also, we will set the admin of our contract.

```solidity
constructor(address vrfCoordinator, address link, bytes32 _keyhash, uint256 _fee)
VRFConsumerBase(vrfCoordinator, link)
{
   keyHash = _keyhash;
   fee = _fee;
   admin = msg.sender;
}
```
We also define a modifier to limit some methods to be called from admin only.

```solidity
modifier onlyAdmin {
  require(msg.sender == admin, "Only admin can call this function");
  _;
}
```
Then, we define a function to create lotteries:

```solidity
function createLottery(uint256 _ticketPrice,uint256 _seconds) payable public onlyAdmin {
    require(_ticketPrice > 0, "Ticket price must be greater than 0");
    require(_seconds > 0, "Lottery time must be greater than 0");
    Lottery memory lottery = Lottery({
    lotteryId: lotteryId.current(),
    participants: new address[](0),
    prize: 0,
    ticketPrice: _ticketPrice,
    winner: address(0),
    isFinished: false,
    endDate: block.timestamp + _seconds * 1 seconds
    });
    lotteries[lotteryId.current()] = lottery;
    lotteryId.increment();
    emit LotteryCreated(lottery.lotteryId,lottery.ticketPrice,lottery.prize,lottery.endDate);
}
```

As you can see, we are validating that the `ticketPrice` and `seconds` are greater than 0. Then, we create a new instance of a lottery and put it into our mapping using the current value of our `lotteryId` as key. Next, we increment the `lotteryId` for the next lottery and emit an event to notify the creation of a lottery.

>Also, note that I'm using `block.timestamp` as a reference to establish the end of the lottery, but depending on your contract, you should consider using `block.number` and [block times](https://etherscan.io/chart/blocktime) to calculate time. [This](https://medium.com/@phillipgoldberg/smart-contract-best-practices-revisited-block-number-vs-timestamp-648905104323) is a good lecture if you are interested in this topic.

Next, we implement the participate function:

```solidity
function participate(uint256 _lotteryId) public payable {
        Lottery storage lottery = lotteries[_lotteryId];
        require(block.timestamp < lottery.endDate,"Lottery participation is closed");
        require(lottery.ticketPrice == msg.value, "Value must be equal to ticket price");
        lottery.participants.push(msg.sender);
        lottery.prize += msg.value;
        uint256 uniqueP = participations[_lotteryId][msg.sender];
        if(uniqueP == 0) {
            uniqueParticipations[_lotteryId]++;
        }
        participations[_lotteryId][msg.sender]++;
        
        emit PrizeIncreased(lottery.lotteryId, lottery.prize);
    }
```
This function will let players participate in the lottery by sending some ETH equals to the `ticketPrice` when the lottery has not ended. We also keep track of players with multiple participations to avoid requesting a random number when we only have a single player.

Next, is the declareWinner function:

```solidity
function declareWinner(uint256 _lotteryId) public onlyAdmin {
        Lottery storage lottery = lotteries[_lotteryId];
        require(block.timestamp > lottery.endDate,"Lottery is still active");
        require(!lottery.isFinished,"Lottery has already declared a winner");
        if(uniqueParticipations[_lotteryId] == 1) {
            require(lottery.participants[0] != address(0), "There has been no participation in this lottery");
            lottery.winner = lottery.participants[0];
            lottery.isFinished = true;
            lottery.winner.call{value: lottery.prize }("");
            require(success, "Transfer failed");
        } else {
            require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK");
            bytes32 requestId = requestRandomness(keyHash, fee);
            lotteryRandomnessRequest[requestId] = _lotteryId;
            emit RandomnessRequested(requestId,_lotteryId);
        }
    }
```
This function verifies that all the requirements have been met before declaring a winner. If we have a single player, we transfer the "prize" back to that player. Otherwise, we validate if the contract has enough LINK to request the random number to the oracle. If there is enough LINK, we call the `requestRandomness` function with `keyHash` and `fee` as the arguments. Then, we map the `requestId` to the `lotteryId` because when the random number comes back within the `fulfillRandomness` function it will only have the requestId and the random number, so without the mapping we won't be able to determine the winner of the right lottery.

Almost done, we define the fulfillRandomness function:

```solidity
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        uint256 _lotteryId = lotteryRandomnessRequest[requestId];
        Lottery storage lottery = lotteries[_lotteryId];
        uint256 winner = randomness.mod(lottery.participants.length);
        lottery.isFinished = true;
        lottery.winner = lottery.participants[winner];
        delete lotteryRandomnessRequest[requestId];
        delete uniqueParticipations[_lotteryId];
        lottery.winner.call{value: lottery.prize }("");
        emit WinnerDeclared(requestId,lottery.lotteryId,lottery.winner);
    }
```

Notice that we override the function's implementation to declare the winner with the random number. Then we are using mod (modulo) from the SafeMath library to get a number between 0 and the length of participations - 1. Finally, we pick the winner using the new number and declare the winner. Also, we save some gas by deleting some of the mapping values :).

Great! We now have our contract completed, but how do we deploy it? how do we test it if we don't have LINK in our local environment and can't interact with the VRFCoordinator?

### Deployment

At the beginning of this tutorial, we installed hardhat-deploy plugin to help us with the deployment. Before using it, let's compare it with the default deployment method from Hardhat. Hardhat deployments require you to create a script within the scripts folder. The script looks like this:

```js
const Greeter = await hre.ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, Hardhat!");
await greeter.deployed();
```
And you run it with:

```sh
npx hardhat run scripts/deploy.js
```
On the other hand, hardhat-deploy will read our deployment scripts from the **deploy** folder.

hardhat-deploy extends Hardhat Runtime Environment (HRE) by including 4 new fields:

**getNamedAccounts**: hardhat-deploy plugin extends Hardhat config to associate [names to addresses](https://github.com/wighawag/hardhat-deploy#configuration) and have them configured per chain. This function returns those names and addresses. 

**getUnnamedAccounts**: similar to getNamedAccounts but returns accounts with no name.

**deployments**: contains functions to access deployments.

**getChainId**: fetch the current chain id.

The previous script would be written like this:

```js
module.exports = async ({ getNamedAccounts, deployments, getChainId }) => {
  const { deploy, log } = deployments;
  const { deployer } = await getNamedAccounts();

  log("Deploying Greeter");

  await deploy("Greeter", {
      from: deployer,
      log: true,
      args: ["Hello, Hardhat!"],
   });
  }
};

module.exports.tags = ["main"];
```

We are including `tags`, a list of string values that can be referenced to execute either a single script or a group of them. Moreover, it allows us to establish dependencies between deployments which could be handy when writing complex deployment procedures.

Then, you can deploy it with:

```sh
npx hardhat deploy 
```

Ok, let's see this in action. First, create the deploy folder at the root of our hardhat project.

```sh
mkdir deploy
```

To test the Chainlink VRF, we need to deploy the LINK token in our HardHat environment and mock the `VRFCoordinador` to simulate the random number request. Fortunately, the Chainlink contracts package already comes with the contracts we need. We will create new contracts and import the Chainlink contracts to avoid copying all the code into our project.

Let's create the new contracts:

```sh
mkdir contracts/external
touch contracts/external/LinkToken.sol
touch contracts/external/VRFCoordinatorMock.sol
```

LinkToken.sol
```solidity
pragma solidity ^0.4.0;
import "@chainlink/contracts/src/v0.4/LinkToken.sol";
```
VRFCoordinatorMock.sol
```solidity
pragma solidity ^0.6.0;
import "@chainlink/contracts/src/v0.6/tests/VRFCoordinatorMock.sol";
```
Now, we code our deployment script

```sh
touch contracts/00_Deploy_External.js
```
00_Deploy_External.js
```js
module.exports = async ({ getNamedAccounts, deployments, getChainId }) => {
  const { deploy, log } = deployments;
  const { deployer } = await getNamedAccounts();
  const chainId = await getChainId();

  if (chainId == 31337) {
    log("Local Network Detected, Deploying external contracts");
    const linkToken = await deploy("LinkToken", { from: deployer, log: true });
    await deploy("VRFCoordinatorMock", {
      from: deployer,
      log: true,
      args: [linkToken.address],
    });
  }
};

module.exports.tags = ["all", "mocks", "main"];
```

This deployment script will be executed when any of the `tags` are referenced in the deploy command or in tests. But, it will deploy the `LinkToken` and `VRFCoordinatorMock` contracts only when the chainId is **31337**, which is the chain Id of our HardHat environment node. There's no need to deploy it on the others supported chains because the contracts are already there.

Do you remember that our contract constructor has four input parameters required by ChainLink? Where do we get those values from? the answer is [here](https://docs.chain.link/docs/vrf-contracts/)

To include these values in our project, we create a config file:

```sh
mkdir config
touch config/chainlink.config.js
```

chainlink.config.js
```js
const config = {
  // Hardhat local network
  // Mock Data (it won't work)
  31337: {
    name: "hardhat",
    keyHash:
      "0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4",
    fee: "0.1",
    fundAmount: "10000000000000000000",
  },
  // Rinkeby
  4: {
    name: "rinkeby",
    linkToken: "0x01BE23585060835E02B77ef475b0Cc51aA1e0709",
    vrfCoordinator: "0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B",
    keyHash:
      "0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311",
    fee: "0.1",
    fundAmount: "2000000000000000000",
  },
};

module.exports = {
  config
};
```

```sh
touch contracts/01_Deploy_Lottery.js
```
01_Deploy_Lottery.js
```js
const { config } = require("../config/link.config");

module.exports = async ({ getNamedAccounts, deployments, getChainId, ethers }) => {
  const { deploy, get, log } = deployments;
  const { deployer } = await getNamedAccounts();
  const chainId = await getChainId();
  let linkToken;
  let linkTokenAddress;
  let VRFCoordinatorMock;
  let vrfCoordinatorAddress;
  let additionalMessage = "";

  if (chainId == 31337) {
    linkToken = await get("LinkToken");
    VRFCoordinatorMock = await get("VRFCoordinatorMock");
    linkTokenAddress = linkToken.address;
    vrfCoordinatorAddress = VRFCoordinatorMock.address;
    additionalMessage =
      " --linkaddress " +
      linkTokenAddress +
      " --fundadmount " +
      config[chainId].fundAmount;
  } else {
    linkTokenAddress = config[chainId].linkToken;
    vrfCoordinatorAddress = config[chainId].vrfCoordinator;
  }
  const keyHash = config[chainId].keyHash;
  const fee = config[chainId].fee;

  const lottery = await deploy("LotteryGame", {
    from: deployer,
    args: [
      vrfCoordinatorAddress,
      linkTokenAddress,
      keyHash,
      ethers.utils.parseUnits(fee, 18),
    ],
    log: true,
  });

  log("Run the following command to fund contract with LINK:");
  log(
    "npx hardhat fund-link --contract " +
      lottery.address +
      " --network " +
      config[chainId].name +
      additionalMessage
  );
  log("----------------------------------------------------");
};

module.exports.tags = ["all", "main"];
```

If we are in our local environment (chain id 31337), the script will get the address of our local LinkToken and VRFCoordinator contracts and use them to deploy our Lottery contract. On the other hand, if we deploy to non-local networks, we will use the addresses from the config file.

Also, we log a message to remind the user to run the fund-link command to send LINK to our Lottery contract.

```sh
npx hardhat deploy

Local Network Detected, Deploying Mocks
deploying "LinkToken" (tx: 0x596222d755670dab88fa5005d6e6f25097b05ccef8810d13ec5b6c8adb02e05d)...:
deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 1279067 gas
deploying "VRFCoordinatorMock" (tx: 0xa7a3d8b4f67818b87d496938f4075c3c46f04d392e7ae2758b4c72807e215d3f)...:
deployed at 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 with 363662 gas
deploying "LotteryGame" (tx: 0x92d0807c1a47825c8197d00ec44425c23e4499b84bbd91d82308e03363659e37)...:
deployed at 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
.........
```
Great! We have deployed all the contracts into our local environments. Now, let's focus on testing.

### Testing

You can find all the tests in the repository. Here I will only focus on VRF testing.

Let's start by creating our unit test file:

```sh
touch test/lottery_unit_test.js
```
lottery_unit_test.js
```js
const { expect } = require("chai");
const { ethers, getChainId, deployments } = require("hardhat");
const { config, autoFundCheck } = require("../config/link.config");

describe("LotteryGame Unit Tests", () => {
  let LotteryGame;
  let lotteryGame;
  let LinkToken;
  let linkToken;
  let chainId;
  let deployer;
  let user2;
  let user3;
  const DAY = 3600 * 24;
  before(async () => {
    [deployer, user2, user3] = await ethers.getSigners();
    chainId = await getChainId();
    await deployments.fixture(["main"]);
    LinkToken = await deployments.get("LinkToken");
    linkToken = await ethers.getContractAt("LinkToken", LinkToken.address);
    LotteryGame = await deployments.get("LotteryGame");
    lotteryGame = await ethers.getContractAt(
      "LotteryGame",
      LotteryGame.address
    );
  });
it("Should Declare a winner", async () => {
    const networkName = config[chainId].name;
    const additionalMessage = " --linkaddress " + linkToken.address;
    if (
      await autoFundCheck(
        lotteryGame.address,
        networkName,
        linkToken.address,
        additionalMessage
      )
    ) {
      await hre.run("fund-link", {
        contract: lotteryGame.address,
        linkaddress: linkToken.address,
      });
    }

    await lotteryGame.createLottery(ethers.utils.parseEther("0.0005"), DAY * 1);
    await lotteryGame.connect(user2).participate(0, {
      value: ethers.utils.parseEther("0.0005"),
    });
    await lotteryGame.connect(user3).participate(0, {
      value: ethers.utils.parseEther("0.0005"),
    });
    await ethers.provider.send("evm_increaseTime", [3600 * 24 * 2]);
    const tx = await lotteryGame.declareWinner(0);
    const txReceipt = await tx.wait();
    const requestId = txReceipt.events[2].topics[1];

    // eslint-disable-next-line no-unused-expressions
    expect(requestId).to.be.not.null;
  });
});
```
We start the unit test script by deploying our contracts with `await deployments.fixture(["main"])`. In this case, we are deploying all the scripts that include the tag "main". Then, we get the reference to our `Lottery` Contract and the `LinkToken`.

Next, we define the test itself and start by running the fund-link command to fund our Lottery contract with LINK using `hre.run`. Then, we create a lottery and impersonate some users to participate in it. Since we only can declare a winner after the lottery has ended, we will jump forward in time with `await ethers.provider.send("evm_increaseTime", [3600 * 24 * 2])`. Finally, we declare the winner and since we can't expect a random number, the only way we have to validate that the random number was requested is to read events from the transaction receipt.

The transaction receipt from `const txReceipt = await tx.wait();` contains an event array with all the events emitted by our contract method call. Each event will have an array of topics up to four elements where the first element will be the hash obtained from the function signature and the rest will be indexed arguments. You can read more about this [here](https://docs.soliditylang.org/en/v0.8.11/abi-spec.html?highlight=topics#events).

There are four events that will be emitted with our method call:

- events[0]: Transfer(address,address,uint256) from **ERCBasic.sol**
- events[1]: Transfer(address,address,uint256,bytes) from ERC677 called from **ERC677Token.sol**
- events[2]: RandomnessRequest(address,bytes32,uint256) from **VRFCoordinatorMock.sol**
- events[3]: WinnerDeclared from **Lottery.sol**

So, if the random number was successfully requested, a RandomnessRequest event should be emitted with the second indexed argument not equal to 0.

```js
const requestId = txReceipt.events[2].topics[1];
expect(requestId).to.be.not.null;
```
If you wonder were ERCBasic, ERC677Token contracts are, you can find them inside the @chainlink/contracts package.

Next, we run our unit test script:
```sh
npx hardhat test ./test/lottery_unit_test.js
```

The integration script is similar to the previous one. But we need to fund our contract with LINK from the network we will deploy our contract to. We can use Chainlink [faucets](https://faucets.chain.link/) to fund our wallets with LINK and then run fund-link command to send LINK to our contract. 

**Warning:** It is essential to select the correct network when requesting LINK. Otherwise, you won't be able to send LINK into your contract.

![Chainlink Faucet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4f6i3zkfr6x4ooecx0n.png)
 
Then, we deploy our contract to a testnet:

```sh
touch test/lottery_integration_test.js
```
lottery_integration_test.js
```js
describe("Lottery Integration Tests", () => {
  let LotteryGame;
  let lotteryGame;
  let chainId;
  let deployer;
  let user2;
  let user3;
  before(async () => {
    LotteryGame = await deployments.get("LotteryGame");
    lotteryGame = await ethers.getContractAt(
      "LotteryGame",
      LotteryGame.address
    );
    [deployer, user2, user3] = await ethers.getSigners();
  });
  it("Should receive the random number from the Oracle", async () => {
    const createLotteryTx = await lotteryGame.createLottery(120, {
      value: ethers.utils.parseEther("0.0005"),
    });
    await createLotteryTx.wait();
    console.log("Lottery created", new Date());
    const length = await lotteryGame.getLotteryCount();
    const lengthNumber = length.toNumber() - 1;
    console.log("Current lottery id", lengthNumber);
    const lottery = await lotteryGame.getLottery(lengthNumber);
    console.log("EndDate: ", new Date(lottery.endDate * 1000));
    const participateTx = await lotteryGame
      .connect(user2)
      .participate(lengthNumber, {
        value: ethers.utils.parseEther("0.0005"),
      });
    console.log(participateTx);
    await participateTx.wait();
    console.log("User2 participated", new Date());
    const lottery2 = await lotteryGame.getLottery(lengthNumber);
    console.log(lottery2);
    await new Promise((resolve) => setTimeout(resolve, 120000));
    const winnerTx = await lotteryGame.declareWinner(lengthNumber);
    await winnerTx.wait();
    // Give the oracle some minutes to update the random number
    await new Promise((resolve) => setTimeout(resolve, 180000));
    const lotteryAfter = await lotteryGame.getLottery(lengthNumber);
    console.log(lotteryAfter);
    expect(lotteryAfter.winner).to.not.be.eq(
      "0x0000000000000000000000000000000000000000"
    );
  });
});

```
Finally, we run our integration test script:
```sh
npx hardhat test ./test/lottery_unit_test.js
```
The result should be:
```sh
✓ Should receive the random number from the Oracle
  1 passing (6m)
```

### Conclusion

This ended up being a more extensive tutorial than I expected. When I started playing with Chainlink VRF I was worried about testing and even read VRFConsumerBase contract and its dependencies to understand how it worked. 

I encourage you to read about hardhat-deploy plugin. It has many other features we haven't covered here that could be helpful in your future projects.

In future posts, I'll be writing about smart contracts upgradability patterns and IPFS. If you have some questions about this tutorial or suggestions for future posts, leave some comments below.