Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

Lottery Project Compiling: A Complete Tutorial

In this tutorial, We are going to finally compile the lottery project. After making sure that it has been successfully compiled, we will run all the written tests to make sure everything works correctly in the contract. In the end, we will deploy the Decentralized Application on the Rinkeby test network. To be able to interact with the deployed contract, we will get some test Link tokens from Rinkeby Faucet.

Compiling the Lottery Project Dapp

In this part, we are going to finally compile, run and test our Lottery Dapp. brownie compile The result should be: Brownie v1.18.1 - Python development framework for Ethereum 1.33MiB [00:03, 402kiB/s] New compatible solc version available: 0.7.0 Compiling contracts... Solc version: 0.7.0 Optimizer: Enabled Runs: 200 EVM Version: Istanbul WARNING: Unable to compile OpenZeppelin/[email protected] due to a CompilerError - you may still be able to import sources from the package, but will be unable to load the package directly. Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.4.26+commit.4563c3fc 100%|██████████████████████████████████████████████████████████████████████| 5.24M/5.24M [00:10<00:00, 498kiB/s] solc 0.4.26 successfully installed at: /home/mohamad/.solcx/solc-v0.4.26 Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.6.6+commit.6c089d02 100%|██████████████████████████████████████████████████████████████████████| 8.92M/8.92M [00:23<00:00, 378kiB/s] solc 0.6.6 successfully installed at: /home/mohamad/.solcx/solc-v0.6.6 Compiling contracts... Solc version: 0.6.12 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/Ownable - OpenZeppelin/[email protected]/Context - smartcontractkit/[email protected]/LinkTokenReceiver - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/AggregatorInterface - smartcontractkit/[email protected]/AggregatorV2V3Interface - smartcontractkit/[email protected]/AggregatorV3Interface - smartcontractkit/[email protected]/ChainlinkRequestInterface - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - Lottery - MockOracle - MockV3Aggregator Compiling contracts... Solc version: 0.4.26 Optimizer: Enabled Runs: 200 EVM Version: Byzantium Generating build data... - smartcontractkit/[email protected]/ERC677Token - smartcontractkit/[email protected]/ERC20 - smartcontractkit/[email protected]/ERC20Basic - smartcontractkit/[email protected]/ERC677 - smartcontractkit/[email protected]/ERC677Receiver - smartcontractkit/[email protected]/BasicToken - smartcontractkit/[email protected]/SafeMathChainlink - smartcontractkit/[email protected]/StandardToken - LinkToken Compiling contracts... Solc version: 0.6.6 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - VRFCoordinatorMock Generating interface ABIs... Project has been compiled. Build artifacts saved at /home/mohamad/smartcontract_lottery/build/contracts

Applying the Tests

Now, we apply our tests with following command in the terminal: brownie test -k test_get_entrance_fee Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.42s ======================================== Terminating local RPC client...

Test Number 2

brownie test -k test_cant_enter_unless_started Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 3

brownie test -k test_can_start_and_enter_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 4

brownie test -k test_can_end_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.34s ======================================== Terminating local RPC client...

Test Number 5

brownie test -k test_can_pick_winner_correctly Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.53s ======================================== Terminating local RPC client…

Deploying Our Lottery Contract

All the tests have been successfully run. Now it is time to finally deploy our contract: brownie run scripts/deploy_lottery.py Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 SmartcontractLotteryProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0x8dece070c593b0dcea9caa4740f89c80ff2fe29744f8e6b6ed26bdf879c7ec9f Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 MockV3Aggregator.constructor confirmed Block: 1 Gas used: 430659 (3.59%) MockV3Aggregator deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Transaction sent: 0xab119cd52792e53c56eee35f433c3f4b8ae409e8a5d397ac2dd16aa2d9d88e27 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 LinkToken.constructor confirmed Block: 2 Gas used: 669136 (5.58%) LinkToken deployed at: 0x602C71e4DAC47a042Ee7f46E0aee17F94A3bA0B6 Transaction sent: 0xb549afbc42364cb3e72877bf6d3634d655fbe0b608a09e921a3f881983dff488 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 2 VRFCoordinatorMock.constructor confirmed Block: 3 Gas used: 276395 (2.30%) VRFCoordinatorMock deployed at: 0xE7eD6747FaC5360f88a2EFC03E00d25789F69291 Deployed! Transaction sent: 0xbb65e39ea0771823b51b07af3e19122d0a9be34b51e768a536a215a9571fdcc7 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 3 Lottery.constructor confirmed Block: 4 Gas used: 897156 (7.48%) Lottery deployed at: 0x6951b5Bd815043E3F842c1b026b0Fa888Cc2DD85 Deployed lottery! Transaction sent: 0xdada264c84b388a9674518ac1fff2a6e7c2851f9adf5dc7ecd871bed9d0c8629 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 4 Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) The lottery is started! Transaction sent: 0x9556bd5fecc58be55b36e84b6612ae264d00fe685e4862f0458846a30e305e2e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 5 Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) You entered the lottery! Transaction sent: 0x94f77b768b68faefacdc57046a42caadf5d954a0d0e089bb551f6594cb44037e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 6 LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Fund contract! LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Transaction sent: 0x6591ec2cc1bfcddb9aea2f80e2723a773506e71bc8b649d58cf02c9708da1847 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 7 Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) 0x0000000000000000000000000000000000000000 is the new winner! Terminating local RPC client...

Getting Some Test Link

By the way, before the deployment of this contract, make sure you have some Link Rinkeby tokens in your Metamask wallet. If you don’t have any, you can first add a Link Rinkeby token from Rinkeby Etherscan (By entering the decimals and contract address and so on). Then use Rinkeby Faucet which gives you 10 Link tokens. By using this link, you can get a Rinkeby Link token to be able to test your project:

https://docs.chain.link/docs/link-token-contracts/#rinkeby

Deploying Lottery Project on the Rinkeby Testnet

After running all the tests successfully and deploying it on Ganache-CLI, it is finally time to deploy our Lottery smart contract on Rinkeby chain, and inter-act with it using Rinkeby etherscan. To do so, in the terminal, we type: brownie run scripts/deploy_lottery.py --network rinkeby Result: Brownie v1.18.1 - Python development framework for Ethereum SmartcontractLotteryProject is the active project. Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0xd0172e3f2d1caf7a56b7a1f88b5b95d152843f38f8e2f0bba68d2a5c3b534df7 Gas price: 1.000006975 gwei Gas limit: 995904 Nonce: 54 Lottery.constructor confirmed Block: 10461557 Gas used: 905368 (90.91%) Lottery deployed at: 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 Waiting for https://api-rinkeby.etherscan.io/api to process contract... Verification submitted successfully. Waiting for result... Verification pending... Verification complete. Result: Pass - Verified Deployed lottery! Transaction sent: 0x7b803fc2bedd3a1be5fa689e527e317baa4177c35ba589c1ea69ce3d6834298c Gas price: 1.000008155 gwei Gas limit: 31572 Nonce: 55 Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) The lottery is started! Transaction sent: 0x7f2f38f4cfc0d34753c2635e8477b4c32118c3211ea3a3e6613a034a83242f99 Gas price: 1.000008155 gwei Gas limit: 97460 Nonce: 56 Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) You entered the lottery! Transaction sent: 0xe1c566be0f8aff444154cf2d655cfc75082d83a7d13b9fb04f4315db3cece877 Gas price: 1.000008524 gwei Gas limit: 56992 Nonce: 57 LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Fund contract! LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Transaction sent: 0x585048e0b048833c35f3bd8257425e1e6f1f781cbd507df97abd756e7343c606 Gas price: 1.000008079 gwei Gas limit: 174062 Nonce: 58 Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) 0x25E681EE76469E4cF846567b772e94e082907117 is the new winner!

Rinkeby Etherscan

Now, we can copy the and paste 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 address in Rinkeby etherscan to be able to interact with your smart contract:

https://rinkeby.etherscan.io

Once you enter Etherscan and paste your address, you will be able to see your address, ABI of the contract, dependency contracts, contract creation code, and so on.

Interacting with the Deployed Contract

To interact with the deployed smart contract, head over to Read the contract and write the contract.

You can read some of the details of the public variables after deployment in the Read contract section:

You can also run another lottery using Write Contract section:

Connecting to Metamask Wallet

But, before writing anything in there, make sure you connect to your wallet by clicking on Connect to Web3 button:

Now you can start the lottery:

Enter the lottery:

And execute other functions:

And that’s it. You have written a lottery smart contract, deployed it on a test net, Rinkeby chain, and other networks, and learned how to interact with it using Rinkeby Etherscan!

Lottery Project: Done!

In this tutorial, we have managed to deploy the contract on Rinkeby Testnet and interact with it from the address of the deployment on the Rinkeby Etherscan. To make this interaction possible from Etherscan, we connected the Metamask account of the admin of the contract to this website and applied all the stages of the lottery from the contract section.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Lottery Project Using Brownie: A Full Scale Dapp

In this article, we are going to get started with the lottery project using Brownie. The main purpose of a lottery project in every network is to check the reliability of the randomness and use it for different purposes such as the lottery itself. In the lottery project, we are going to create a decentralized application using Brownie. In the end, we will be able to run the smart contract via Etherscan.

Using Brownie for Lottery Project

In this tutorial, we are going to first write a smart contract related to a lottery and write scripts related to testing and deploying the smart contract. We also want to make it a full-scale decentralized application, meaning that it is going to be an end-to-end Dapp with easy to use user experience. Every lottery needs a random variable. As randomness is a very complicated concept when it is going to be applied on the internet and here we are going to run it via the blockchain, it must be protected from hacks and cheating. What makes randomness really complex, is that we are dealing with deterministic variables rather than probabilistic ones. As a result, we are going to use some Chainlink tools to cover this complexity.

In this first part, we only focus on the lottery.sol related to the smart contracts, its dependency files, and the Brownie-config.yaml in addition to seeing how the Solidity scripts are written and how they work.

Getting Started with the Lottery Project

It is also important to notice that this tutorial is written to explain the GitHub repository published by Patrick Alpha C more explicitly and solve the probable issues that the programmers might face when running and facing bugs related to the Solidity version or the configuration of the project. With that said let’s get started:

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

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";


We have first declared the version of Solidity and the license comment before that. Also, we have imported 3 necessary Solidity libraries including AggregatorV3Interface which is used for fetching the Ethereum price from Chainlink oracle, openzeppelin Ownable.sol which is used for determining the owner of the contract and finally the VRFConsumer.sol which is used to get a random number.

contract Lottery is VRFConsumerBase, Ownable {
        address payable[] public players;
	address payable public recentWinner;
	uint256 public randomness;
	uint256 public usdEntryFee;
	AggregatorV3Interface internal ethUsdPriceFeed;
	enum LOTTERY_STATE {
		OPEN,
		CLOSED,
		CALCULATING_WINNER
	}
	LOTTERY_STATE public lottery_state;
	uint256 public fee;
	bytes32 public keyhash;
	event RequestedRandomness(bytes32 requestId);

	constructor(address _priceFeedAddress,
			address _vrfCoordinator,
			address _link,
			uint256 _fee,
			bytes32 _keyhash
			) public VRFConsumerBase(_vrfCoordinator, _link) {
		usdEntryFee = 50 * (10**18);
		ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);
		lottery_state = LOTTERY_STATE.CLOSED;
		fee = _fee;
		keyhash = _keyhash;
	}
}

We start the contract named Lottery and determine their declarations ownable, VRFConsumerBase. Inside the contract we define the variables that we are going to work with including:
  • The array of the addresses of the participants in the lottery defined here as players (declared as payable and public),
  • The recent winner who has won the prize in the last lottery,
  • The randomness which is the random number we receive from VRFC,
  • UsdEntryFee which is the minimum amount that a player needs to participate in the lottery, ethUsdPriceFeed which is the conversion rate of ETH to USD.
The lottery state is declared as Enum and has 3 states Open: Close, and Calculating winner.
  1. Open is when everyone can participate in the lottery,
  2. Closed is when nobody can participate
  3. Calculating is used when the random number related to the winner is being calculated.
We also have a constructor for some variables which are going to be explained later.

function enter() public payable {
	// $50 minimum
	require(lottery_state == LOTTERY_STATE.OPEN);
	require(msg.value >= getEntranceFee(), "Not enough ETH!");
	players.push(msg.sender);
}


The above function is related to the entrance to the lottery. At first, the state of the lottery is open and we check if the players pay the minimum entrance fee to participate in it.

function getEntranceFee() public view returns (uint256) {
	(, int256 price, , , ) = ethUsdPriceFeed.latestRoundData();
	uint256 adjustedPrice = uint256(price) * 10**10;
	// 18 decimals
	// $50, $2,000 / ETH
	// 50/2,000
	// 50 * 100000 / 2000
	uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
	return costToEnter;
}


The above function is responsible for checking the price of the Ethereum in USD and determining the price of participation in ETH.

function startLottery() public onlyOwner {
	require(lottery_state == LOTTERY_STATE.CLOSED,"Can't start a new lottery yet!");
	lottery_state = LOTTERY_STATE.OPEN;
}


The above function starts the lottery and before that checks if the lottery has not yet got closed.

function endLottery() public onlyOwner {
	lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
	bytes32 requestId = requestRandomness(keyhash, fee);	
	emit RequestedRandomness(requestId);
}


The above function gives the authority to only the owner of the contract. This function also determines the winner to end the lottery. This process is done by requesting a random number.

function fulfillRandomness(bytes32 _requestId, uint256 _randomness)internal override{
	require(lottery_state == LOTTERY_STATE.CALCULATING_WINNER,"You aren't there yet!");
	require(_randomness > 0, "random-not-found");
	uint256 indexOfWinner = _randomness % players.length;
	recentWinner = players[indexOfWinner];
	recentWinner.transfer(address(this).balance);
	// Reset
	players = new address payable[](0);
	lottery_state = LOTTERY_STATE.CLOSED;
	randomness = _randomness;
}


The above function requires the lottery state to be in the calculating winner. It also checks whether the given random number is positive. Then it uses the remainder operator to calculate the id of the winner from the random number it gets. After that, it transfers the balance of the contract to the player that has won the lottery And finally changes the state of the lottery to closed.

Configuration

Notice that up to here you need to enter the following into the Brownie-config.yaml:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'
dotenv: .env
networks:
	default: development
	development:
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
	rinkeby:
		vrf_coordinator:'0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B'
		eth_usd_price_feed:'0x8A753747A1Fa494EC906cE90E9f37563A8AF630e'
		link_token: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709'
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
		verify: True
	mainnet-fork:
		eth_usd_price_feed:'0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
		verify: False
wallets:
	from_key: ${PRIVATE_KEY}



Up to here, we only need the following to be able to compile the Brownie project:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'


In the next parts, we are going to see how we can deploy and test this smart contract on different networks.

Deployment of the Lottery Project Using Brownie

In this section, we are going to continue the lottery project and focus on its deployment of it. The main python file as always is the deploy.py where we are going to deploy the four stages of a lottery: deploy the lottery, start the lottery, enter the lottery and end the lottery. The prize here is paid with a Chainlink token. As a result, we will add the ChainlinkTokenInterface contract to the contracts directory.

Managing the Folders of the Lottery Project Directory

In order to deploy the smart contract that we have just written, we need to complete the deployment and test folders and also add some other files like the dependencies of our smart contract. As always, the first step is to tyoe in the terminal: brownie init Then, you will see the folders and files that are created afterward. Then it is time to write our deploy.py file. As you know every lottery contract has 4 stages, deploying the lottery, starting it, entering the lottery, and ending it. So accordingly, we have 4 functions:

from scripts.helpful_scripts import get_account, get_contract, fund_with_link
from brownie import Lottery, network, config
import time

def deploy_lottery():
	account = get_account()
	lottery = Lottery.deploy(get_contract("eth_usd_price_feed").address, 						 
		get_contract("vrf_coordinator").address,
		get_contract("link_token").address,
		config["networks"][network.show_active()]["fee"],
		config["networks"][network.show_active()]["keyhash"],
		{"from": account},
		publish_source=config["networks"][network.show_active()].get("verify", False),)								
	print("Deployed lottery!")
	return lottery


In every contract deployment, we should determine some of the specifications related to a contract such as contract addresses (address of the dependency contracts) and some config specifications about every network.

def start_lottery():
	account = get_account()
	lottery = Lottery[-1]
	starting_tx = lottery.startLottery({"from": account})
	starting_tx.wait(1)
	print("The lottery is started!")


The above function starts the lottery. At first, it gets the account address which uses the get account function from helpful_scripts.py, then it gets the latest lottery smart contract, After that, it starts the lottery transaction using the startLottery function inside the contract.

def enter_lottery():
	account = get_account()
	lottery = Lottery[-1]
	value = lottery.getEntranceFee() + 100000000
	tx = lottery.enter({"from": account, "value": value})
	tx.wait(1)
	print("You entered the lottery!")


The above function also gets the account and the latest lottery contract records, then determine the value of the entrance fee (notice that 10000000 that has been added up with the entrance fee, is not a big number as it is in the Wei unit). Then, we enter the participant by using the enter function from the lottery.sol contract.

def end_lottery():
	account = get_account()
	lottery = Lottery[-1]
	# fund the contract
	# then end the lottery
	tx = fund_with_link(lottery.address)
	tx.wait(1)
	ending_transaction = lottery.endLottery({"from": account})
	ending_transaction.wait(1)
	time.sleep(180)
	print(f"{lottery.recentWinner()} is the new winner!")


In the above function, again get the account and the lottery contract, fund the winner with some link token (using the function written in helpful_scripts.py), and ends the transaction using the function in the lottery contract.sol.

def main():
	deploy_lottery()
	start_lottery()
	enter_lottery()
	end_lottery()


In the above main function, we apply other functions in the sequence of a lottery.

Helpful_Scripts.py

Now, it is time to go after helpful_scripts.py file and explain the codes:

from brownie import (accounts, network, config, MockV3Aggregator, VRFCoordinatorMock, LinkToken, 
	Contract, interface,)
FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

def get_account(index=None, id=None):
	if index:
		return accounts[index]
	if id:
		return accounts.load(id)
	if ( network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS or network.show_active() in 
			FORKED_LOCAL_ENVIRONMENTS):
		return accounts[0]
	return accounts.add(config["wallets"]["from_key"])


The above function determines the address of the available account according to the active network.

contract_to_mock = {"eth_usd_price_feed": MockV3Aggregator,"vrf_coordinator": VRFCoordinatorMock,
	"link_token": LinkToken,}
def get_contract(contract_name):
	contract_type = contract_to_mock[contract_name]
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		if len(contract_type) <= 0:
			# MockV3Aggregator.length
			deploy_mocks()
			contract = contract_type[-1]
			# MockV3Aggregator[-1]
	else:
		contract_address = config["networks"][network.show_active()][contract_name]
		contract = Contract.from_abi(contract_type._name, contract_address, contract_type.abi)
	return contract


The above function will grab the contract addresses from the Brownie config when defined. Otherwise, it will deploy a mock version of that contract, and return that mock contract.

DECIMALS = 8
INITIAL_VALUE = 200000000000
def deploy_mocks(decimals=DECIMALS, initial_value=INITIAL_VALUE):
	account = get_account()
	MockV3Aggregator.deploy(decimals, initial_value, {"from": account})
	link_token = LinkToken.deploy({"from": account})
	VRFCoordinatorMock.deploy(link_token.address, {"from": account})
	print("Deployed!")


The above function deploys the mock version of the 2 contracts: 1. VRFCoordinator and 2. V3Aggregator contracts.

def fund_with_link(contract_address, account=None, link_token=None,amount=100000000000000000): # 0.1 LINK
	account = account if account else get_account()
	link_token = link_token if link_token else get_contract("link_token")
	tx = link_token.transfer(contract_address, amount, {"from": account})
	tx.wait(1)
	print("Fund contract!")
	return tx


The above function funds the winner with a link token. Notice that we should create a file in the interfaces folder for the link token contract and name it LinkTokenInterface.sol. You can copy and paste the contract below:

Link Token Interface Contract

The below contract which is called LinkTokenInterface.sol, should be added to the contacts folder. We use the functions of this contract in the helpful_scripts.py and deploy.py to fund the winner of the lottery with some ChainLink tokens. If you look at the contract in detail, you will see that the functions and methods are the same as of an ERC-20 token contract.

pragma solidity ^ 0.6.6;

interface LinkTokenInterface {
	function allowance(address owner, address spender) external view returns (uint256 remaining);
	function approve(address spender, uint256 value) external returns (bool success);
	function balanceOf(address owner) external view returns (uint256 balance);
	function decimals() external view returns (uint8 decimalPlaces);
	function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
	function increaseApproval(address spender, uint256 subtractedValue)external;
	function name() external view returns (string memory tokenName);
	function symbol() external view returns (string memory tokenSymbol);
	function totalSupply() external view returns (uint256 totalTokensIssued);
	function transfer(address to, uint256 value) external returns (bool success);
	function transferAndCall(
		address to,
		uint256 value,
		bytes calldata data
		) external returns (bool success);
	function transferFrom(
		address from,
		address to,
		uint256 value
		) external returns (bool success);
}


Compiling Our Complete Lottery Project Using Brownie for Final Deployment

In this section, following the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully. The focus of this section is on the testing of the contract, where we are going to test the different functionalities of different stages of the lottery project. In the following of the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully.

Brownie_config.sol

There are some .sol dependencies that need to be copied and pasted into the contracts/test folder so that we can use them to deploy our main lottery smart contract. So make sure you copy them from this link and paste them into your directory.

.env File

Also, in the .env file enter the private key of your test Metamask account (Do not use a Metamask wallet with real crypto in it), your Etherscan Token, and Infura ID.

export WEB3_INFURA_PROJECT_ID=''
export PRIVATE_KEY=''
export ETHERSCAN_TOKEN=''


Testing the Contract Functionalities

Now, every standard smart contract deployment needs testing and this one is not an exception, we start our test with test_lottery_unit.py:

from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,get_account,fund_with_link,
	get_contract,)
from brownie import Lottery, accounts, config, network, exceptions
from scripts.deploy_lottery import deploy_lottery
from web3 import Web3
import pytest

def test_get_entrance_fee():
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	# Arrange
	lottery = deploy_lottery()
	# Act
	# 2,000 eth / usd
	# usdEntryFee is 50
	# 2000/1 == 50/x == 0.025
	expected_entrance_fee = Web3.toWei(0.025, "ether")
	entrance_fee = lottery.getEntranceFee()
	# Assert
	assert expected_entrance_fee == entrance_fee


The above test considers the price of ETH is 2000 dollars and the entrance fee is 50 dollars so the 0.025 ether is required to participate in the lottery. as a result, we expect the entrance fee to be 0.025 ether, so we call the getEn-tranceFee() function to check the validity of the result.

def test_cant_enter_unless_started():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	# Act / Assert
	with pytest.raises(exceptions.VirtualMachineError):
		lottery.enter({"from": get_account(), "value": lot-tery.getEntranceFee()})


The above test checks whether the participant can enter or not at the times the lottery has been closed or has not opened yet.

def test_can_start_and_enter_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	# Act
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	# Assert
	assert lottery.players(0) == account


The above test checks whether the player who has participated in the lottery is recorded in the list of lottery players in other words it checks if the player has entered or not.

def test_can_end_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	assert lottery.lottery_state() == 2



The above test checks whether the end lottery function works. To do so, it first passes all the other tests and applies the other stages of the lottery and at the end tests the lottery state.

def test_can_pick_winner_correctly():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": get_account(index=1), "value": 	lot-tery.getEntranceFee()})
	lottery.enter({"from": get_account(index=2), "value": 	lot-tery.getEntranceFee()})
	fund_with_link(lottery)
	starting_balance_of_account = account.balance()
	balance_of_lottery = lottery.balance()
	transaction = lottery.endLottery({"from": account})
	request_id = transaction.events["RequestedRandomness"]["requestId"]
	STATIC_RNG = 777
	get_contract("vrf_coordinator").callBackWithRandomness(request_id, STATIC_RNG, lottery.address,
		{"from": account})
	# 777 % 3 = 0
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0
	assert account.balance() == starting_balance_of_account + bal-ance_of_lottery
	


The above test uses all of the operations of the lottery that have been tested to test the correctness of the winner picking. To do so, it first enters an account into the lottery and at the end, tests whether the winner is in the same account, whether the lottery contract balance has turned 0 as a result of sending money to the winner, and whether the winner’s account has been added up with the balance of the lottery contract.

from brownie import network
import pytest
from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,
	get_account, fund_with_link,)
from scripts.deploy_lottery import deploy_lottery
import time

def test_can_pick_winner():
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	time.sleep(180)
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0


The above test also does the same process in a little bit different manner.

It is important to know that we should do all of the tests one by one to be able to debug the functions. As you can see, we started our first test with the first stage of the contract.

Final Word on Lottery Project Deployment Using Brownie

Firstly, we have managed to get started with the lottery project and have written the smart contract for it. In addition to that, we have added some dependency contracts like VRFConsumerBase, Openzeppelin, and V3AggregatorInterface.

Secondly, we have managed to write the deploy.py and helpful_scripts.py to interact with the lottery smart contract and deploy the different stages of a lottery such as deploying the lottery, starting it, entering it using different accounts, and ending it. In the end, the winner is going to be awarded some Chainlink tokens.

Thirdly, we have managed to complete the whole lottery project script file so that we can finally compile and deploy the smart contract. In most of the parts of this tutorial, we focused on testing the contract scripts for testing the different functionalities related to the different stages of the contract.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we're working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development