Site icon Chainstack

Lottery smart contract on Cronos blockchain

Cronos Smart Contract 1 1024x542 logo

Introduction

Cronos is an EVM-compatible blockchain that uses Proof-Of-Authority (POA) to deliver a faster and cheaper finality to its users. As an open-source Layer 1 blockchain, Cronos aims to combine its EVM-compatible infrastructure with other blockchains built on top of the Cronos SDK to enable fast and cheap transfers of assets.

In this tutorial, we will learn how to build a lottery smart contract using Solidity, and how to deploy and verify smart contracts on the Cronos blockchain using Hardhat.

Setting up a Hardhat project

Before writing our smart contract, we need to set up our dev environment. To create a Hardhat project:

  1. Create a folder named cronosHardhat (or any other name you like).
  1. Open the new folder in the terminal and run the command:
    npm install --save-dev hardhat
    This command downloads the latest version of Hardhat into your directory.
  1. To create a sample Hardhat project, run npx hardhat.
  1. This will open up a UI interface in your terminal.

Select ‘Create a JavaScript project’, and press enter through all the options. This will create a sample Hardhat project in your terminal, and Hardhat will automatically download all the additional dependencies for this sample project using ‘hardhat-toolbox’.

Taking a look at Hardhat’s project structure

You should now have a basic Hardhat project set up in your directory. Let us take a brief look at some folders and files within the project layout:

Writing a smart contract in Hardhat

Inside the contracts folder, create a new file named Lottery.sol.
Inside the file, paste the following code:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Lottery {
    address public owner;
    address payable[] public players;
    bool public lotteryInProgress;
    uint public lotteryId;
    mapping (uint => address payable) public lotteryHistory;

    constructor() {
        owner = msg.sender;
        lotteryId = 1;
    }

    function startLottery() public onlyowner{
        lotteryInProgress=true;
    }

    function alreadyEntered() private view returns(bool){
        for(uint i=0; i< players.length; i++){
            if(players[i]==msg.sender)
            return true;
        }
        return false;
    } 

    function enterLottery() public payable {
        require(lotteryInProgress,"No lottery event is currently in progress");
        require(msg.value > 0.5 ether, "Please deposit at least 0.5 Ether");
        require(msg.sender!=owner, "As owner, you can't enter the lottery");
        require(alreadyEntered()==false, "You have already entered in the Lottery");
        require(players.length<=10, "This lottery has maximum participation. Please wait for the next event");

        players.push(payable(msg.sender));
    }
    function getRandomNumber() private view returns (uint) {
        return uint(keccak256(abi.encodePacked( block.timestamp, block.difficulty, block.gaslimit)));
    }

    function pickWinner() public onlyowner {
        require(lotteryInProgress,"No lottery event is currently in progress");
        require(players.length>0, "can't pick winners without participants");
        uint index = getRandomNumber()%players.length;
        players[index].transfer(address(this).balance - 0.1 ether);

        lotteryHistory[lotteryId] = players[index];
        lotteryId++;       
        lotteryInProgress=false;
        players = new address payable[](0);
    }

    modifier onlyowner() {
      require(msg.sender == owner, "Only owner can call this function");
      _;
    }

    function getWinnerByLottery(uint lottery) public view returns (address payable) {
        return lotteryHistory[lottery];
    }

    function getBalance() public view returns (uint) {
        return address(this).balance/(10**18);
    }

    function numOfPlayers() public view returns (uint) {
        return players.length;
    }

    function getPlayers() public view returns (address payable[] memory) {
        return players;
    }
}

Let us go over the code quickly:

After pasting in the code, save the file and go to your terminal. Inside there, run the command:

npx hardhat compile

This will compile all the smart contracts in your contracts folder and generate ABIs for them in the artifacts folder. We now have a smart contract ready to deploy.

Setting up our env variables

To deploy our smart contracts, we basically need two things:

Tutorial on how to set up a public chain node with Chainstack

While it’s possible to directly put your keys into your hardhat.config file, we do not recommend doing this since you could end up pushing this data to online repositories, which can lead to your private keys being compromised. We will use the dotenv package to export our keys securely.

To install the dotenv package in your directory, open your terminal, and run:

  1. npm i dotenv: To install the dotenv package into your directory.
  1. touch .env: To create a new .env file in your directory. Go to your newly created dotenv file and put in your private keys. This is what your dotenv file should look like right now:
RPC_URL=https://nd-XXXXXXXXX
PRIVATE_KEY=0x2fqXXXXXXXXXXXXXXXXXXXXXu7
API_KEY=XW10AXXXXXXXXXXXXXXXXXG5

Save the dotenv file and run this command in your terminal:

source .env

This command will load your sensitive data onto your command line. You can run echo $RPC_URL in your terminal to verify if the data is accessible by Hardhat.

Configuring hardhat.config

As explained before, this file contains all of your configuration for your hardhat project. Go to hardhat.config in your root directory, and paste this code after deleting its contents:

require("@nomicfoundation/hardhat-toolbox");
require('dotenv').config();
require("@nomiclabs/hardhat-etherscan");
require("@cronos-labs/hardhat-cronoscan");


module.exports = {
  solidity: "0.8.17",
  defaultNetwork: "Cronos_testnet",
  networks: {
    Cronos_testnet: {
        url: `${process.env.RPC_URL}`,
        accounts: [process.env.PRIVATE_KEY]
    },
},
    etherscan: {
        apiKey: {
         cronosTestnet: `${process.env.API_KEY}`,
  },
},
};

A few things happen here:
hardhat-toolbox is a recent addition to Hardhat that bundles together a variety of commonly used Hardhat packages and plugins into one import.
• The hardhat-cronoscan plugin is used in conjunction with the hardhat-etherscan plugin in order to verify contracts deployed on Cronos mainnet or testnet.
• We can define multiple blockchain connections inside the networks object. We can also define a default network that Hardhat will fall back to in case we don’t specify a particular network in the command line. This helps in case we have multiple networks defined within the config file, but mostly only use a particular network.
• We define the Cronoscan API key inside the etherscan object.
hardhat.config supports a lot more configuration parameters, but this is all we need for now.

You might notice, however, that we haven’t installed the hardhat-cronoscan plugin yet. To install the plugin, go to the terminal in your root directory, and run:

npm i --save-dev @nomiclabs/hardhat-etherscan@^3.1.0 @cronos-labs/hardhat-cronoscan

Now save your config file, and run the npx hardhat compile command in your terminal once again. This will make sure that all your contracts are compiled according to the latest configurations of our Hardhat project.

Testing in Hardhat

  1. Smart contracts are meant to handle money. Thus it is highly recommended that one tests their smart contracts before deploying them to a live blockchain network.
  1. For testing, we will connect to the Hardhat network, which is basically a mock blockchain created on your system by Hardhat, and we will test our smart contract using Mocha and Chai libraries through an automated testing script.
  1. To begin, open a new terminal in the same directory, and run the command:
    npx hardhat node
    This will give us a simulated blockchain in our command line with a list of accounts filled with a bunch of fake ETH.
    How convenient!
  1. We need to install one final plugin to that allows us to leverage the full power of the Chai library. Open your original terminal, and run:
npm install --save-dev @nomicfoundation/hardhat-chai-matchers
  1. This will add some Ethereum specific capabilites to the Chai library. We will look into this in more detail below. Finally, open hardhat-config.js and add this line to the top of the file:
require("@nomicfoundation/hardhat-chai-matchers");
  1. We are now ready to test our Solidity code.
  1. Open the test folder, and delete any files present inside. Create a new file by the name of Lottery.js and add the following code inside:
const { expect } = require("chai");
const hre = require("hardhat");

describe("Lottery", function () {

    let owner, player1, player2, lottery;

    before(async () => {

        [owner, player1, player2] = await ethers.getSigners();
        provider = await ethers.getDefaultProvider();
        const Lottery = await hre.ethers.getContractFactory("Lottery");
        console.log("Deploying Contract for testing.......")
        lottery = await Lottery.deploy();
        await lottery.deployed();
    });

    it("Should set owner correctly", async function () {

        const ownerAddress = await lottery.owner();
        console.log("owner from contract is : ", ownerAddress);
        expect(await ownerAddress).to.equal(owner.address);
    });

    it("Non-owner shouldn't be able to start lottery", async function () {

        await expect(lottery.connect(player1).startLottery()).to.be.revertedWith("Only owner can call this function");
    });

    it("Should start lottery event", async function () {

        let lotteryInProgress = await lottery.lotteryInProgress();
        console.log("Current value of lotteryInProgress is : ", lotteryInProgress);
        await lottery.startLottery();
        console.log("New value of lotteryInProgress is : ", await lottery.lotteryInProgress());
        expect(await lottery.lotteryInProgress()).to.equal(true);
    });

    it("Player 1 should be 1st participant", async function () {

        await lottery.connect(player1).enterLottery({ value: ethers.utils.parseEther("2.0") });
        let participant1 = await lottery.players(0);

        player1Balance = await ethers.provider.getBalance(player1.address);
        console.log("Remaining ETH with player1: ", player1Balance / (10 ** 18));
        expect(participant1).to.equal(player1.address);
    });

    it("Player 2 should be 2nd participant", async function () {

        await lottery.connect(player2).enterLottery({ value: ethers.utils.parseEther("3.0") });
        let participant2 = await lottery.players(1);
        player2Balance = await ethers.provider.getBalance(player2.address);
        console.log("Remaining ETH with player2: ", player2Balance / (10 ** 18));
        expect(participant2).to.equal(player2.address);
    });
    it("pickWinner() function should not be called by non-owner", async function () {

        await expect(lottery.connect(player1).pickWinner()).to.be.revertedWith("Only owner can call this function");
    });

    it("pickWinner() function should work correctly", async function () {

        lottery.pickWinner();
        LotteryId = lottery.lotteryId();
        let Winner = await lottery.lotteryHistory(LotteryId);
        let WinnerBalance = await ethers.provider.getBalance(Winner);
        console.log("Winner is: ", Winner);
        console.log("Winner address is:", WinnerBalance / (10 ** 18));
        expect(await Winner).to.satisfy(function (anyWinner) {
            if ((anyWinner == player1.address) || (anyWinner == player2.address)) {
                return true;
            } else {
                return false;
            }
        });

    });

});

This is a lot. Let us go over this code and what it does carefully:

Once you are ready, save the file. In your main terminal, run:

npx hardhat test --network localhost

This command will run your test scripts on the Hardhat network, provided you actually have the network running on your system. We did that when we ran the npx hardhat node command.

This is what your terminal should look like now:

Testing in Hardhat

All our tests are passing, we are now ready to deploy our smart contract to a live network.

Deploying our smart contract

We now have our private keys ready to go and have tweaked the config file as per our requirements. We also just tested our smart contract that is now ready to go.
This is good stuff already. However, to actually deploy a smart contract through Hardhat, we need to create a deployment script.
Inside the scripts folder, go to deploy.js and delete everything inside it.

Now paste the following code inside the same file:

const hre = require("hardhat");

async function main() {
  const CronosLottery = await hre.ethers.getContractFactory("Lottery");
  console.log("Deploying your contract, please Wait.....");
  const cronosLottery = await CronosLottery.deploy();
  await cronosLottery.deployed();

  console.log("CronosToken deployed to:", cronosLottery.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

This is a simple deploy script. We access the deploy function through Hardhat’s runtime environment.
To run the deploy script, open your terminal and run:

npx hardhat run --network Cronos_testnet scripts/deploy.js

We log the address of the contract we just deployed in our console.

Go to Cronos testnet explorer and search for your contract address. You will notice that the contract code is not yet visible. That is because we haven’t verified our contract. Let us move on to that.

Verifying our smart contract

To verify our contract through Hardhat’s command line, we need to use Hardhat’s verify command. We will pass the address of the deployed contract along with the RPC URL of the blockchain network.
If you recall, we have already defined a Cronos testnet inside our config file.
Open your terminal, and run:

npx hardhat verify --network Cronos_testnet {Contract Address}

This might take a while, but Hardhat will return a link to your verified contract in the terminal if the verification is successful. Open the link and you should now be able to interact with the contract right from the Cronos testnet explorer itself.

Conclusion

In this article, we learned how to set up a Hardhat project and how to deploy and verify a smart contract on Cronos testnet.
Please note that you can tweak the parameters inside Hardhat’s config file to deploy and verify the same contract on Cronos mainnet with a few minor changes. The Cronoscan API key works for both the mainnet and the testnet.

If you are interested in exploring Cronos further, you can deploy your own Cronos node with Chainstack and work with that, or you may just head over to our blog for some more cool web3 tutorials.
Happy coding!

Have you already explored what you can achieve with Chainstack? Get started for free today.

Exit mobile version