Eth-Lottery - Solidity, Hardhat
Tags: web3, ethereum, solidity, dapps
Published: 2022-03-28 01:31:38

Few months ago i started reading about web3 technology and fell in love with ethereum and smart contracts. I decided to learn solidity to write smart contracts.After couple of weeks the first full fledged smart contract which i came across was Lottery Smart Contract which was just a basic smart contract which used to allow anyone to join the lottery by depositing some eth and the lottery Manager(owner of the lottery smart contract) would pick a random winner.But the approach which it took to pick the winner was not actually a true random winner.

```
function random() private view returns(uint){
  return uint(keccak256(abi.encode(block.difficulty,block.timestamp, players)));
}
```
Problem with the above snippet is that relying on the block variables for the source of entropy(randomness) is not the safe approach, As miners can easily manipulate those values.

We will look at how we can solve this issue by generating the random number off-chain using chailink oracle [VRF v2](https://docs.chain.link/docs/chainlink-vrf/) .

## Oracle
Blockchain oracles are entities that connect blockchains to external systems, thereby enabling smart contracts to execute based upon inputs and outputs from the real world. 


`Tech Stack`
- Solidity With Hardhat Framework

`Prequisites`
- NodeJS
- Fair understanding of how solidity works
- Love for web3 ❤️

## Let's Start
Create a new directory i'll call it eth-lottery why not it sounds cool 😉.

```
npm init -y // inside your eth-lottery directory
```
Next, Let's Install [Hardhat](https://hardhat.org/getting-started/)

```
npm install --save-dev hardhat
```
Next we initialize hardhat project.

```
> 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.9.2

? 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

```
Once the hardhat project is initialized, Next we install [hardhat-deploy](https://github.com/wighawag/hardhat-deploy) which will make our life easier with deployments and tests😁

`npm install hardhat-deploy`

Next, Lets configure our accounts, Hardhat comes with a cool feature NamedAccounts which allows us to access our account with name, rather than accessing by specific index `account[0]`

Next, In your .env add your account private key and [alchemy](https://www.alchemy.com/) / [infura](https://infura.io/) url which we will be later required for deployements.
For now ignore the `VRF_SUBSCRIPTION_ID` we will come to that later
```
RINKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/<YOUR ALCHEMY KEY>
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
PRIVATE_KEY_USER_2=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
PRIVATE_KEY_USER_3=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
VRF_SUBSCRIPTION_ID=000
```

Next, update your network section in `hardhat.config.js`.

```
  networks: {
    ganache:{
      url: "http://127.0.0.1:8545",
      chainId:1337
    },
    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,
    },
  }
```
Next, Lets install the required libraries.

```
npm install @chainlink/contracts @openzeppelin/contracts
```
We will be using chainlink/contracts to implement the vrf and from openzeppeline i'll be using a contract call `Counters` which helps to get a counter that can be incremented and decremented we will be using it as our lotteryID.

Next, Create two files `Lottery.sol` & `LotteryData.sol` in contracts folder. It Should look like this.

```javascript
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;


import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "./LotteryData.sol";

contract Lottery is VRFConsumerBaseV2{
    VRFCoordinatorV2Interface COORDINATOR;
    LinkTokenInterface LINKTOKEN;
    LotteryData LOTTERY_DATA;

    using Counters for Counters.Counter;

    using SafeMath for uint256;

    Counters.Counter private lotteryId;

    uint public totalAllowedPlayers = 10;

    address public lotteryManager;

    mapping(uint256 => uint256) private lotteryRandomnessRequest;
    bytes32 private keyHash;
    uint64 immutable s_subscriptionId;
    uint16 immutable requestConfirmations = 3;
    uint32 immutable callbackGasLimit = 100000;
    uint256 public s_requestId;

    event RandomnessRequested(uint256,uint256);
    
    //To emit data which will contain the requestId-from chainlink vrf, lotteryId, winnder address
    event WinnerDeclared(uint256 ,uint256,address);

    //To emit data which will contain the lotteryId, address of new-player & new Price Pool
    event NewLotteryPlayer(uint256, address, uint256);

    //To emit data which will contain the id of newly created lottery
    event LotteryCreated(uint256);


    //custom Errors
    error invalidValue();
    error invalidFee();
    error lotteryNotActive();
    error lotteryFull();
    error lotteryEnded();
    error playersNotFound();
    error onlyLotteryManagerAllowed();

     constructor(
        bytes32 _keyHash,
        uint64 subscriptionId, 
        address _vrfCoordinator, 
        address _link,
        address _lotteryData
        ) VRFConsumerBaseV2(_vrfCoordinator){
        lotteryId.increment();   
        lotteryManager = msg.sender;
        COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator);
        LINKTOKEN = LinkTokenInterface(_link);
        s_subscriptionId = subscriptionId;
        keyHash = _keyHash;
        LOTTERY_DATA = LotteryData(_lotteryData);
    }

    modifier onlyLotteryManager {
        if(msg.sender != lotteryManager) revert onlyLotteryManagerAllowed();
        _;
    }

    function getAllLotteryIds() public view returns(uint256[] memory){
        return LOTTERY_DATA.getAllLotteryIds();
    }

    function startLottery() public payable onlyLotteryManager {
        LOTTERY_DATA.addLotteryData(lotteryId.current());
        lotteryId.increment();
        emit LotteryCreated(lotteryId.current());
    }

    function enterLottery(uint256 _lotteryId) public payable {
        (uint256 lId, 
        uint256 ticketPrice, 
        uint256 prizePool, 
        address[] memory players, 
        address winner, 
        bool isFinished) = LOTTERY_DATA.getLottery(_lotteryId);

        if(isFinished) revert lotteryNotActive();
        if(players.length > totalAllowedPlayers) revert lotteryFull();
        if(msg.value < ticketPrice) revert invalidFee();
        uint256  updatedPricePool = prizePool + msg.value;
        LOTTERY_DATA.addPlayerToLottery(_lotteryId, updatedPricePool, msg.sender);
        emit NewLotteryPlayer(_lotteryId, msg.sender, updatedPricePool);
    }

    function pickWinner(uint256 _lotteryId) public onlyLotteryManager {

        if(LOTTERY_DATA.isLotteryFinished(_lotteryId)) revert lotteryEnded();

        address[] memory p = LOTTERY_DATA.getLotteryPlayers(_lotteryId);
        if(p.length == 1) {
            if(p[0] == address(0)) revert playersNotFound();
            //require(p[0] != address(0), "no_players_found");
            LOTTERY_DATA.setWinnerForLottery(_lotteryId, 0);
            payable(p[0]).transfer(address(this).balance);
            emit WinnerDeclared(0,_lotteryId,p[0]);
        } else {
            //LINK is from VRFConsumerBase
            s_requestId = COORDINATOR.requestRandomWords(
                keyHash,
                s_subscriptionId,
                requestConfirmations,
                callbackGasLimit,
                1 // number of random numbers
            );
            lotteryRandomnessRequest[s_requestId] = _lotteryId;
            emit RandomnessRequested(s_requestId,_lotteryId);
        }
    }

    function fulfillRandomWords(uint256 requestId, uint256[] memory randomness) internal override {
        uint256 _lotteryId = lotteryRandomnessRequest[requestId];
        address[] memory allPlayers = LOTTERY_DATA.getLotteryPlayers(_lotteryId);
        uint256 winnerIndex = randomness[0].mod(allPlayers.length);
        LOTTERY_DATA.setWinnerForLottery(_lotteryId, winnerIndex);
        delete lotteryRandomnessRequest[requestId];
        payable(allPlayers[winnerIndex]).transfer(address(this).balance);
        emit WinnerDeclared(requestId,_lotteryId,allPlayers[winnerIndex]);
    }

    function getLotteryDetails(uint256 _lotteryId) public view returns(
        uint256,
        uint256,
        uint256 ,
        address[] memory,
        address ,
        bool
        ){
            return LOTTERY_DATA.getLottery(_lotteryId);
    }
}
```

```javascript
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.11;


contract LotteryData {

    struct LotteryInfo{
        uint256 lotteryId;
        uint256 ticketPrice;
        uint256 prizePool;
        address[] players;
        address winner;
        bool isFinished;
    }
    mapping(uint256 => LotteryInfo) public lotteries;

    uint256[] public allLotteries;

    uint public lotteryTicketPrice = 0.5 ether;

    address private manager;
    bool private isLotteryContractSet;
    address private lotteryContract;
    constructor(){
        manager = msg.sender;
    }

    error lotteryNotFound();
    error onlyLotteryManagerAllowed();
    error actionNotAllowed();

    modifier onlyManager(){
        if(msg.sender != manager) revert onlyLotteryManagerAllowed();
        _;
    }

    modifier onlyLoterryContract(){
        if(!isLotteryContractSet) revert actionNotAllowed();
        if(msg.sender != lotteryContract) revert onlyLotteryManagerAllowed();
        _;
    }

    function updateLotteryContract(address _lotteryContract) external onlyManager{
        isLotteryContractSet = true;
        lotteryContract = _lotteryContract;
    }

    function getAllLotteryIds() external view returns(uint256[] memory){
        return allLotteries;
    }


    function addLotteryData(uint256 _lotteryId) external onlyLoterryContract{
        LotteryInfo memory lottery = LotteryInfo({
            lotteryId: _lotteryId,
            ticketPrice: lotteryTicketPrice,
            prizePool: 0,
            players: new address[](0),
            winner: address(0),
            isFinished: false
        });
        lotteries[_lotteryId] = lottery;
        allLotteries.push(_lotteryId);
    }

    function addPlayerToLottery(uint256 _lotteryId, uint256 _updatedPricePool, address _player) external onlyLoterryContract{
        LotteryInfo storage lottery = lotteries[_lotteryId];
        if(lottery.lotteryId == 0){
            revert lotteryNotFound();
        }
        lottery.players.push(_player);
        lottery.prizePool = _updatedPricePool;
    }


    function getLotteryPlayers(uint256 _lotteryId) public view returns(address[] memory) {
        LotteryInfo memory tmpLottery = lotteries[_lotteryId];
        if(tmpLottery.lotteryId == 0){
            revert lotteryNotFound();
        }
        return tmpLottery.players;
    }

    function isLotteryFinished(uint256 _lotteryId) public view returns(bool){
        LotteryInfo memory tmpLottery = lotteries[_lotteryId];
         if(tmpLottery.lotteryId == 0){
            revert lotteryNotFound();
        }
        return tmpLottery.isFinished;
    }

    function getLotteryPlayerLength(uint256 _lotteryId) public view returns(uint256){
        LotteryInfo memory tmpLottery = lotteries[_lotteryId];
         if(tmpLottery.lotteryId == 0){
            revert lotteryNotFound();
        }
        return tmpLottery.players.length;
    }

    function getLottery(uint256 _lotteryId) external view returns(
        uint256,
        uint256,
        uint256 ,
        address[] memory,
        address ,
        bool
        ){
            LotteryInfo memory tmpLottery = lotteries[_lotteryId];
            if(tmpLottery.lotteryId == 0){
                revert lotteryNotFound();
            }
            return (
                tmpLottery.lotteryId,
                tmpLottery.ticketPrice,
                tmpLottery.prizePool,
                tmpLottery.players,
                tmpLottery.winner,
                tmpLottery.isFinished
            );
    }

    function setWinnerForLottery(uint256 _lotteryId, uint256 _winnerIndex) external onlyLoterryContract {
        LotteryInfo storage lottery = lotteries[_lotteryId];
        if(lottery.lotteryId == 0){
            revert lotteryNotFound();
        }
        lottery.isFinished = true;
        lottery.winner = lottery.players[_winnerIndex];
    }
}
```
Here we have two smart-contracts `Lottery` & `LotteryData`.I took this approach so that the LotteryData which holds the overall lotteries and other info can be managed separately.

Let's Quickly Go through `LotteryData.sol` first we have a struct mapping. which holds the info to our specific lottery which maps a (uniqueLotteryId => lottery info)
`  struct LotteryInfo{
        uint256 lotteryId;
        uint256 ticketPrice;
        uint256 prizePool;
        address[] players;
        address winner;
        bool isFinished;
    }
   mapping(uint256 => LotteryInfo) public lotteries;
`
Next  we define some custom errors, i'll be using custom errors rather than `require` as it saves up on some gas.

```
 error lotteryNotFound();
 error onlyLotteryManagerAllowed();
 error actionNotAllowed();

```
Next, we have a function called `updateLotteryContract` which can only be called by the owner of this[`LotteryData.sol`] contract which sets the `Lottery.sol` address to the variable
`address private lotteryContract` 
so `LotteryData.sol` can be only accessed and modified by the address set for lotteryContract.

Next we have other functions which i won't be explaining in depth.Which are just to maintain each lottery data which will be modified from our `Lottery.sol`.

Next, Let's Take a look at our `Lottery.sol`
First we import our required contracts

```javascript
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "./LotteryData.sol";
```

Next, We Inherit from VRFConsumerBaseV2

`contract Lottery is VRFConsumerBaseV2`
 As we inherit from VRFConsumerBaseV2 we need to also implement the VRFConsumerBaseV2 constructor which expects `_vrfCoordinator`

```
constructor(
        bytes32 _keyHash,
        uint64 subscriptionId, 
        address _vrfCoordinator, 
        address _link,
        address _lotteryData
        ) VRFConsumerBaseV2(_vrfCoordinator){
        lotteryId.increment();   
        lotteryManager = msg.sender;
        COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator);
        LINKTOKEN = LinkTokenInterface(_link);
        s_subscriptionId = subscriptionId;
        keyHash = _keyHash;
        LOTTERY_DATA = LotteryData(_lotteryData);
    }
```
VRF Coordinator is a contract that is deployed to a blockchain that will check the randomness of each random number returned from a random node.

With VRFv2 we need VRF Subscription which can be created from 
[https://vrf.chain.link/](https://vrf.chain.link/) then add the newly generated subscription id to your `.env`. Now in order to generate a random number using vrf we need make a  request to vrf coordinator.If you take a look at the `pickWinner` function

```javascript
    function pickWinner(uint256 _lotteryId) public onlyLotteryManager {

        if(LOTTERY_DATA.isLotteryFinished(_lotteryId)) revert lotteryEnded();

        address[] memory p = LOTTERY_DATA.getLotteryPlayers(_lotteryId);
        if(p.length == 1) {
            if(p[0] == address(0)) revert playersNotFound();
            //require(p[0] != address(0), "no_players_found");
            LOTTERY_DATA.setWinnerForLottery(_lotteryId, 0);
            payable(p[0]).transfer(address(this).balance);
            emit WinnerDeclared(0,_lotteryId,p[0]);
        } else {
            //LINK is from VRFConsumerBase
            s_requestId = COORDINATOR.requestRandomWords(
                keyHash,
                s_subscriptionId,
                requestConfirmations,
                callbackGasLimit,
                1 // number of random numbers
            );
            lotteryRandomnessRequest[s_requestId] = _lotteryId;
            emit RandomnessRequested(s_requestId,_lotteryId);
      }
}
```
when the lottery manager calls `pickWinner()` we first check if there is just one player in lottery if yes ? we consider the only player player[0] as the winner. Else we request a random number from COORDINATOR which has a function `requestRandomWords` which expects keyHash, s_subscriptionId ,requestConfirmations,callbackGasLimit, and number of random numbers required from oracle.

You can find the keyHash from [here](https://docs.chain.link/docs/vrf-contracts/#rinkeby-testnet)

In previous version of vrf we had to fund the LINK token address before requesting random number. But with VRFv2 we just add our contract in our case `Lottery.sol` as the consumer in subscription manager and maintain sufficient amount of LINK to request random numbers.


![vrf sub screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap0obboc1j7gnl6wq7ar.png)


Next, Once we call the `requestRandomWords` from COORDINATOR. To get the random number we need to implement `fulfillRandomWords()`in our `Lottery.sol` which is then called by the coordinator which passes the random numbers to our lottery contract.

```javascript
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomness) internal override {
        uint256 _lotteryId = lotteryRandomnessRequest[requestId];
        address[] memory allPlayers = LOTTERY_DATA.getLotteryPlayers(_lotteryId);
        uint256 winnerIndex = randomness[0].mod(allPlayers.length);
        LOTTERY_DATA.setWinnerForLottery(_lotteryId, winnerIndex);
        delete lotteryRandomnessRequest[requestId];
        payable(allPlayers[winnerIndex]).transfer(address(this).balance);
        emit WinnerDeclared(requestId,_lotteryId,allPlayers[winnerIndex]);
    }

```
First argument is the `requestId` which we get when we called `requestRandomWords` second argument is randomness array of `uint256[] random numbers` in our case we requested just `1` random number so we access it as `randomness[0]`.

## Let's take it for a spin with hardhat.
i'll quickly go through the deploy scripts which i have written and also the mocks which can be used for testing.

First lets create new directory called config inside that create `chainlink.config.js`

```
const config = {
    // Hardhat local network
    // Mock Data (it won't work)
    31337: {
      name: "hardhat",
      keyHash:
        "0x7c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f3",
      fee: "0.1",
      fundAmount: "10000000000000000000",
    },
    //ganache
    1337: {
      name: "ganache",
      keyHash:
        "0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4",
      fee: "0.1",
      fundAmount: "10000000000000000000",
    },
    // Rinkeby
    4: {
      name: "rinkeby",
      linkToken: "0x01BE23585060835E02B77ef475b0Cc51aA1e0709",
      vrfCoordinator: "0x6168499c0cFfCaCD319c818142124B7A15E857ab",
      keyHash:
        "0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc",
      fee: "0.25",
      fundAmount: "2000000000000000000",
    },
  };


const developmentChains = ["hardhat", "localhost"]
const VERIFICATION_BLOCK_CONFIRMATIONS = 6
  
module.exports = {
    developmentChains,
    VERIFICATION_BLOCK_CONFIRMATIONS,
    config,
};
```


Create two files `00_Deploy_Mocks.js` & `01_Deploy_Lottery.js`

```javascript
//00_Deploy_Mocks.js
const POINT_ONE_LINK = "100000000000000000"
module.exports = async ({getNamedAccounts, deployments, getChainId, network}) => {
    const {deploy, log} = deployments;
    const {deployer} = await getNamedAccounts();
    const chainId = await getChainId();
    log(chainId);
    log(await getNamedAccounts());

    if (chainId == 31337 || chainId == 1337) {
        log("Local network detected! Deploying mocks...")
        const linkToken = await deploy("LinkToken", { from: deployer, log: true })
        await deploy("VRFCoordinatorV2Mock", {
          from: deployer,
          log: true,
          args: [
            POINT_ONE_LINK,
            1e9, // 0.000000001 LINK per gas
          ],
        })
        await deploy("MockOracle", {
          from: deployer,
          log: true,
          args: [linkToken.address],
        })
        log("Mocks Deployed!");
      }
};

module.exports.tags = ["all", "mocks", "main"]
```
Over here we deploy a mock version of `VRFCoordinator` contract called `VRFCoordinatorV2Mock` to simulate the Oracle. We call it MockOracle.

```javascript
//01_Deploy_Lottery.js
const { config,developmentChains,VERIFICATION_BLOCK_CONFIRMATIONS } = require("../config/chainlink.config");
const { network } = require("hardhat")
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 vrfCoordinatorAddress;
  let subscriptionId
  
  if (chainId == 31337 || chainId == 1337) {
    linkToken = await get("LinkToken");
    //log(ethers)
    VRFCoordinatorV2Mock = await get("VRFCoordinatorV2Mock")
    linkTokenAddress = linkToken.address;
    vrfCoordinatorAddress = VRFCoordinatorV2Mock.address;
    const vrfM = await ethers.getContractAt(
      "VRFCoordinatorV2Mock", vrfCoordinatorAddress, await ethers.getSigner()
    );
    const fundAmount = config[chainId]["fundAmount"]
    const transaction = await vrfM.createSubscription()
    const transactionReceipt = await transaction.wait(1)
    subscriptionId = ethers.BigNumber.from(transactionReceipt.events[0].topics[1])
    await vrfM.fundSubscription(subscriptionId, fundAmount)

  } else {
    subscriptionId = process.env.VRF_SUBSCRIPTION_ID
    linkTokenAddress = config[chainId]["linkToken"]
    vrfCoordinatorAddress = config[chainId]["vrfCoordinator"]
  }

  const keyHash = config[chainId].keyHash;
  const waitBlockConfirmations = developmentChains.includes(network.name)
    ? 1
    : VERIFICATION_BLOCK_CONFIRMATIONS

  const lotteryData = await deploy("LotteryData",{
    from:deployer,
    log: true
  });

  const lottery = await deploy("Lottery", {
    from: deployer,
    args: [
      keyHash,
      subscriptionId, 
      vrfCoordinatorAddress, 
      linkTokenAddress,
      lotteryData.address
    ],
    log: true,
    waitConfirmations: waitBlockConfirmations,
  });

  const lData = await ethers.getContractAt(
    "LotteryData", lotteryData.address, await ethers.getSigner()
  );
  await lData.updateLotteryContract(lottery.address);


  log("----------------------------------------------------");
  log("VRF subscriptionId  " +  subscriptionId);
  log("Lottery Data Deployed On " +  lotteryData.address + " network " + network.name);
  log("Lottery Deployed On " +  lottery.address + " network " + network.name);
  log("----------------------------------------------------");


};

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

```
In order to test the oracle locally with mocks we need to fake bunch of flows one is the subscription. which can be done using Mock Contracts.If you take look at [VRFCoordinatorV2Mock](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/mocks/VRFCoordinatorV2Mock.sol) Contract it has bunch of handy functions which we can use to mock the working of oracle vrf coordinator we will use two of those initially 
`createSubscription` & `fundSubscription()`
There you go we have our own mock vrf which we can use locally for testing.

Next, We deploy our `Lottery.sol` & `LotteryData.sol` Contracts.

```
  const keyHash = config[chainId].keyHash;
  const waitBlockConfirmations = developmentChains.includes(network.name)
    ? 1
    : VERIFICATION_BLOCK_CONFIRMATIONS

  const lotteryData = await deploy("LotteryData",{
    from:deployer,
    log: true
  });

  const lottery = await deploy("Lottery", {
    from: deployer,
    args: [
      keyHash,
      subscriptionId, 
      vrfCoordinatorAddress, 
      linkTokenAddress,
      lotteryData.address
    ],
    log: true,
    waitConfirmations: waitBlockConfirmations,
  });

  const lData = await ethers.getContractAt(
    "LotteryData", lotteryData.address, await ethers.getSigner()
  );
  await lData.updateLotteryContract(lottery.address);
```
First i deploy our `LotteryData` contract then the `Lottery` Contract with the same deployer account.Once we deploy it we call the `updateLotteryContract()` function from `LotteryData.sol` Contract to set the lotteryContract address as the deployed `Lottery` contract address, You can consider `LotteryData` contract as the storage for your `Lottery` contract.Then we export our deployments as `tags`.

Tags in hardhat represent what the deploy script acts on. In general it will be a single string value, the name of the contract it deploys or modifies.

## Let's Deploy 🚀

```
> npx hardhat deploy
Local network detected! Deploying mocks...
deploying "LinkToken" (tx: 0x596222d755670dab88fa5005d6e6f25097b05ccef8810d13ec5b6c8adb02e05d)...: deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3 with 1279067 gas
deploying "VRFCoordinatorV2Mock" (tx: 0xec41fc616bb633e16f7b7adbe9174b7155b95768d1e4499311eeccb4c3960e7e)...: deployed at 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 with 1803090 gas
deploying "MockOracle" (tx: 0x6de3453525ba02d0a91fa291924fcd8ac1d638925399254285738b29b0d643c0)...: deployed at 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 with 1131081 gas
Mocks Deployed!
deploying "LotteryData" (tx: 0xd7f72658787d802e4cf8be6ad3069fded940ba0c624b3fda7120bf981b072774)...: deployed at 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 with 1310966 gas
deploying "Lottery" (tx: 0xa0b3e0de0ebf2e114d69e4894cb032a08f0c3877741d0f637ab7ba58c855ba9c)...: deployed at 0x0165878A594ca255338adfa4d48449f69242Eb8F with 1746568 gas
----------------------------------------------------
VRF subscriptionId  1
Lottery Data Deployed On 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 network hardhat
Lottery Deployed On 0x0165878A594ca255338adfa4d48449f69242Eb8F network hardhat
```
This will spin up the hardhats own test block-chain node and deploy the contracts on that node.

Next,Let's write some unit tests 👩‍💻 
Create `lottery_unit_test.js` inside test directory

```javascript
const {expect} = require('chai');
const {ethers, getChainId, deployments} = require('hardhat');

describe("LotteryGame Unit Tests", () => {
    let LotteryGame;
    let lotteryGame;
    let vrfCoordinatorV2Mock;
    let chainId;
    let deployer;
    let user2;
    let user3;
    before(async () => {
      [deployer, user2, user3] = await ethers.getSigners();
      chainId = await getChainId();
      await deployments.fixture(["main"]);
      vrfCoordinatorV2Mock = await deployments.get("VRFCoordinatorV2Mock")
      vrfCMock = await ethers.getContractAt(
        "VRFCoordinatorV2Mock",
        vrfCoordinatorV2Mock.address
      );

      LotteryGame = await deployments.get("Lottery");
      lotteryGame = await ethers.getContractAt(
        "Lottery",
        LotteryGame.address
      );
    });

    it("Should Pick A Pick A Random Winner", async () => {
      const newLottery = await ethers.getContractAt(
        "Lottery", LotteryGame.address, deployer
      );
      await newLottery.startLottery();
      await newLottery.connect(user2).enterLottery(1, {
        value: ethers.utils.parseEther("0.5"),
      });
      await newLottery.connect(user3).enterLottery(1, {
        value: ethers.utils.parseEther("0.5"),
      });

      await expect(newLottery.pickWinner(1))
      .to.emit(newLottery, "RandomnessRequested")

      const requestId = await newLottery.s_requestId()

       // simulate callback from the oracle network
       await expect(
        vrfCMock.fulfillRandomWords(requestId, newLottery.address)
      ).to.emit(newLottery, "WinnerDeclared")
    });
  });
```
Let's go through one of the tests which simulates the Oracle.
`"Should Pick A Pick A Random Winner"`
First we start a lottery called `newLottery` then we enter with two of our test hardhat test accounts.Then we call `pickWinner()` function and expect it to emit event called ("RandomnessRequested") which will return a request_id from our vrfMock contract. Then we make sure our deployed VrfMock emits the event `WinnerDeclared`. which will be emitted once our VrfMock calls the `fulfillRandomWords()`. which we have simulated 

```javascript
// simulate callback from the oracle network
await expect(vrfCMock.fulfillRandomWords(requestId, newLottery.address)
).to.emit(newLottery, "WinnerDeclared")
```
Let's run our tests

```
> npx hardhat test test/lottery_unit_test.js
  LotteryGame Unit Tests
    ✔ Should Pick A Pick A Random Winner (55ms)
  1 passing (2s)
```

The `expect` which we are using is from [chaijs](https://www.chaijs.com/) testing lib.They have some great examples here [chai matchers](https://ethereum-waffle.readthedocs.io/en/latest/matchers.html)

## Deploying to rinkeby test net
This will deploy all our contracts to Rinkeby test net.
```
> npx hardhat deploy --network rinkeby
```

## Resources
As this was a brief overview explaining my experience with smart contract and chainlink oracle. There is Lot that you can improve with these contracts. 

- i recommend you to go through the [hardhat-deploy](https://github.com/wighawag/tutorial-hardhat-deploy) doc which is straight forward easy to understand
- Hardhat [starter-kit](https://github.com/smartcontractkit/hardhat-starter-kit)
- Read [Chainlink docs](https://docs.chain.link/docs/chainlink-vrf/) one of the awesome documentation.

And let everything sink in you

![just a gag](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2n45obr2adh699gwsluy.gif)

## Bonus
i have this full code on my github [here](https://github.com/Hussainzz/Eth-Lottery).You can also find the client implementation which i have done using reactJs.
Feel free to give a ⭐

[https://eth-lottery-3glp1l5k0-hussainzz.vercel.app/](https://eth-lottery-3glp1l5k0-hussainzz.vercel.app/
)

![eth-client-demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1tr0xh6mffloyae95gq.png)







[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-1.svg)](https://www.buymeacoffee.com/shkhussainb)