By Camron Haider - API3 Core Team Twitter: @CamBrazy3 In this tutorial, we’ll be walking through building and deploying a decentralized lottery smart contract in Solidity using . Hardhat In this example use case, anyone can choose a number 1–10,000 and buy a ticket to enter into a weekly lottery. The ticket revenue is collected into a pot in the contract. After 7 days, the contract will allow anyone to trigger the drawing. The contract will then call the for a truly random number generated by quantum mechanics. The pot will be split amongst all users that chose this winning number. If there are no winners, the pot will be rolled over to the next week. Once deployed, the lottery will continue to run and operate itself automatically without any controlling parties! API3 QRNG By the end of this tutorial you should be able to: Deploy a decentralized lottery smart contract to the that uses . Goerli testnet Quantum Randomness Who is this tutorial for? Developers with a basic understanding of the Solidity and Javascript languages that would like to expand their knowledge of building with smart contracts using . oracles In Part 1, we create a centralized lottery smart contract. In Part 2, we decentralize our lottery by integrating the . API3 QRNG Setup Create a folder and open it up in your preferred IDE. We prefer with the . Visual Studio Code Hardhat extension 1. Initialize a Node.js project Create a folder for your project and open it in VSCode. In a terminal, initialize a project by running the following command: npm init -y 2. Install Hardhat is a npm library with a built-in local Ethereum node that allows you to develop smart contracts. Because it will only be used for development purposes, we can install it as a development dependency: Hardhat npm install -D hardhat 3. Initialize the Hardhat project We’ll use the Hardhat CLI to create a boilerplate Web3 project: npx hardhat Follow the prompts to and choose the default options for the rest. Create a JavaScript project When the CLI is done creating the project, you should see a few new files and directories inside of your project. Boilerplate contracts are located in the folder. Tests for that contract are located in the folder. We’ll be deleting these files in the next steps so now would be a good time to look through them. contracts tests Run the test command to see the boilerplate contract in action. npx hardhat test When we run , Hardhat spins up a local node and tests against it before shutting down. This makes it fast and free to execute our contracts. npx hardhat test Writing the Smart Contract Writing the Smart Contract The complete contract code can be found in the Part1 branch 1. In the folder, delete the file and create a file named contracts Lock.sol Lottery.sol 2. Set the solidity version, and start with an empty contract pragma solidity ^0.8.9; contract Lottery {} 3. Add global variables to the contract contract Lottery { uint256 public pot = 0; // total amount of ether in the pot uint256 public ticketPrice = 0.0001 ether; // price of a ticket uint256 public week = 1; // current week counter uint256 public endTime; // unix datetime lottery is closable uint256 public constant MAX_NUMBER = 10000; // max guess } 4. Underneath the global variables, add our error handling error EndTimeReached(uint256 lotteryEndTime); Underneath the errors, add the for tickets and winning numbers mappings The mapping stores the list of addresses that guessed a specified number during a specified week. The mapping stores each weeks winning number. tickets winningNumber mapping(uint256 => mapping(uint256 => address[])) public tickets; mapping(uint256 => uint256) public winningNumber; Mappings can be accessed throughout our code by using for example. winningNumber[weekNumber] 6. Underneath the mappings, add the constructor function When deploying the contract, we’ll need to pass in a unix timestamp of when the lottery will end. We store the value in our global variable from step 3. _endTime endTime constructor(uint256 _endTime) { endTime = _endTime; } 7. Underneath the constructor function, add a function to buy a ticket Users can call this function with a number 1–10,000 and a value of 0.001 ether to buy a lottery ticket. function enter(uint256 _number) external payable { require(_number <= MAX_NUMBER, "Number must be 1-MAX_NUMBER"); if (block.timestamp >= endTime) revert EndTimeReached(endTime); require(msg.value == ticketPrice, "Price is 0.0001 ether"); tickets[week][_number].push(msg.sender); pot += ticketPrice; } We make the function so that it can only be called from an external user. We also make it because users will have to send funds to purchase the ticket. external payable On line 2 we add a statement to prevent users passing in a guess higher than the maximum allowed. Line 3 we throw an error in the case that the current time has passed the . Line 4 we ensure the ticket price was sent in the transaction. [require](https://docs.soliditylang.org/en/v0.4.24/control-structures.html#error-handling-assert-require-revert-and-exceptions) endTime On line 5 we add the user’s address ( ) to our mapping from step 5, then add the revenue from the ticket sales to the . msg.sender tickets pot 8. Create a function to mock picking the winners Before we decentralize our lottery, let’s mock the random number generation so that we can test the contract’s functionality. We’ll be decentralizing this function in of this tutorial by using the . Part 2 API3 QRNG function closeWeek(uint256 _randomNumber) external { require(block.timestamp > endTime, "Lottery has not ended"); winningNumber[week] = _randomNumber; address[] memory winners = tickets[week][_randomNumber]; week++; endTime += 7 days; if (winners.length > 0) { uint256 earnings = pot / winners.length; pot = 0; for (uint256 i = 0; i < winners.length; i++) { payable(winners[i]).call{value: earnings}(""); } } } Since we’re just mocking randomness in this step, we’ll make our function take a argument and make it external. In line 3 we store the “random number” in our mapping. In line 4 we get all of the addresses that chose the winning number and store them in instead of since we won’t be changing any of the data, just referencing it. Next, we increment the counter and . _randomNumber winningNumber memory storage week endTime Then, unless there are no winners this week, divide the pot amongst the winners. Line 9 we set the pot back to 0 ether before we pay out winners on line 11. 9. Create read-only function This function will return the list of addresses that chose the given number for the given week. This will make our lives easier during the testing steps. function getEntriesForNumber(uint256 _number, uint256 _week) public view returns (address[] memory) { return tickets[_week][_number]; } We mark the function as so that it can be used externally and internally if necessary. We also mark it as because no data should be changed when calling this function. public view 10. Create function receive The will be called if funds are sent to the contract. In this case, we need to add these funds to the pot. receive function receive() external payable { pot += msg.value; } Testing the contract If our contract is used in production, users’ real funds will be at stake. That makes thorough testing extremely important in smart contract development. If you haven’t worked with before, I recommend you learn the basics. We’ll mainly be interacting with our contract through testing and scripts. unit tests 1. In the test folder, delete the file and create a file called Lottery.js Lock.js 2. Import npm libraries const { expect } = require("chai"); const { ethers } = require("hardhat"); We’ll be using the library inside of Hardhat which includes some extra capabilities. ethers 3. Add tests We’ll start with a simple deployment test to be sure that the contract is deploying correctly. describe("Lottery", function () { let lotteryContract, accounts, nextWeek; it("Deploys", async function () { const Lottery = await ethers.getContractFactory("Lottery"); accounts = await ethers.getSigners(); nextWeek = Math.floor(Date.now() / 1000) + 604800; lotteryContract = await Lottery.deploy(nextWeek); expect(await lotteryContract.deployed()).to.be.ok; }); }); On line 2, we create empty global variables to store a few values that we’ll be using in multiple tests. In our test, we’ll get our Lottery contract factory that we’ll use to deploy new instances of our contract. We get all of our signers, or wallets that are connected to Hardhat, and store them globally as . We also store globally because we’ll use it later. Deploys accounts nextWeek On line 7 we deploy the contract using from our contract factory. Our constructor takes in 1 argument, , which we’ll pass into . .deploy() endTime .deploy() We use to validate the boolean returned from is truthy. expect().to.be.okay .deployed() We can use to run the test. npx hardhat test Let’s add a few more tests but feel free to add any/all of the relevant tests from the . completed test file describe("Lottery", function () { let lotteryContract, accounts, nextWeek; it("Deploys", async function () { const Lottery = await ethers.getContractFactory("Lottery"); accounts = await ethers.getSigners(); nextWeek = Math.floor(Date.now() / 1000) + 604800; lotteryContract = await Lottery.deploy(nextWeek); expect(await lotteryContract.deployed()).to.be.ok; }); it("Users enter between 1-3", async function () { for (let account of accounts) { let randomNumber = Math.floor(Math.random() * 3); await lotteryContract .connect(account) .enter(randomNumber, { value: ethers.utils.parseEther("0.0001") }); const entries = await lotteryContract.getEntriesForNumber(randomNumber, 1); expect(entries).to.include(account.address); } }); it("Choose winners", async function () { const winningNumber = 2; // Move hre 1 week in the future let endTime = await lotteryContract.endTime(); await ethers.provider.send("evm_mine", [Number(endTime)]); const winners = await lotteryContract.getEntriesForNumber(winningNumber, 1); let balanceBefore = await ethers.provider.getBalance(winners[0]); await lotteryContract.closeWeek(winningNumber); const balanceAfter = await ethers.provider.getBalance(winners[0]); expect(balanceAfter.gt(balanceBefore)).to.be.true; }); }); Run to try it out. npx hardhat test Conclusion All of the completed code for Part 1 can be found in the of the repo. Part 1 Branch In Part 1 of this tutorial we learned how to build and test a lottery smart contract using Hardhat. The problem is, our function is not secure, as it is public and can be called by anyone accessing the smart contract after the week's lottery ends. In its current state, anyone could enter the lottery and then pass their number into the function to steal the pot. Because a decentralized online gambling application is only as feasible as the degree to which it is fair, secure, and unexploitable, it requires a source of random number generation that is unbiased and tamper-proof. closeWeek closeWeek In Part 2, we’ll be decentralizing our lottery contract and addressing the security concerns using the . Any participant will still be able to call the function, but will not be able to provide a winning number. Instead, our contract will call the API3 QRNG to generate a truly random number that will be used to determine the winner(s). Once deployed, the lottery will continue to run and operate itself automatically without any controlling parties! API3 QRNG closeWeek Also Published Here