Project Objectives – by building this project you will:
  • Manage participants with a struct, a mapping and an array.
  • Chain several modifiers to enforce business rules.
  • Generate a pseudo-random index with keccak256 – and understand why on-chain randomness is risky.
Synopsis

Traditional lotteries depend on intermediaries to sell tickets, hold the money and pick the winner, so players must trust that the draw is fair and the prize is paid. A smart contract can make the rules – who may buy, how many tickets exist, how the winner is chosen and how much is won – public and automatic.

In this project the deploying account is the BlockAdmin. Other users buy one ticket each (the admin may not buy) until the ticket limit is reached. The admin then declares the result: the contract computes a pseudo-random index, selects that ticket holder as the winner and records the total collected amount as the winning amount.

Contract Architecture
PartDetails
UserRecord.solFile-level struct UserRecord { address useraddress; string username; uint amount; uint winningAmount; }
LotteryContract.solContract LotteryContract; imports contracts/UserRecord.sol
stateTICKET_AMOUNT = 500, userlimit = 5, userCount, totalBalance, resultDeclared, address[] userAddressList
mappingmapping(uint => UserRecord) public lotteryUserList – ticket number → buyer
modifierscheckBlockAdmin (admin only), checkTicketUser (admin cannot buy), checkTicketUserCount (one ticket per address), checkUserCount (ticket limit), resultStatus (result not yet declared)
lotteryTicketPurchase(name)Stores the buyer, adds TICKET_AMOUNT to totalBalance, increases userCount.
declareLotteryResult()Admin only, once. Computes the winner index and stores winner with winningAmount = totalBalance.
The Random Function

The contract picks a winner index with a hash of values that change from call to call:

uint winnerIndex = uint(keccak256(abi.encodePacked(msg.sender, block.timestamp, startvalue))) % userlimit;
  • abi.encodePacked joins the caller’s address, the block timestamp and a constant into bytes.
  • keccak256 hashes them into a 256-bit number.
  • % userlimit reduces it to an index between 0 and 4.
This is pseudo-random, not secure randomness. The admin chooses when to call the function and the timestamp is known, so the outcome can be predicted or influenced. Real-money lotteries use a verifiable random function (VRF) oracle or a commit–reveal scheme (Chapter 14).
Key Requirements (Concepts Used)
  • Struct declared at file level and imported (Chapters 2, 13).
  • Mapping and dynamic array (Chapters 10, 12).
  • Functions with string parameters in memory (Chapters 9, 15).
  • Several modifiers on one function (Chapter 22).
  • Global variables and hashing: msg.sender, block.timestamp, keccak256, abi.encodePacked (Chapter 14).
How to Run in Remix
  1. Create contracts/UserRecord.sol and contracts/LotteryContract.sol from the resource files.
  2. Compile and deploy LotteryContract from the first account (BlockAdmin).
  3. From five different accounts, call lotteryTicketPurchase with a name. Try buying twice, or buying from the admin account, to see the errors.
  4. From the admin account call declareLotteryResult(), then read winner.
Limitations and Extension Exercises
Observation in the teaching versionImprovement to try
The index uses % userlimit (5). If fewer than 5 tickets were sold, the index can point to an empty record and the “winner” is the zero address.Use % userCount and require at least one player before declaring.
Ticket amounts are numbers only; no Ether is paid or won.Make the purchase function payable, require msg.value == ticketPrice and let the winner withdraw the pot (Chapter 24).
Randomness can be predicted or influenced by the admin.Use a VRF oracle or a commit–reveal scheme.
The duplicate-buyer check loops over all buyers.Use mapping(address => bool) hasTicket.
Exercise solution (one possible approach – randomness still for learning only)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract LotteryV2 {
    address public immutable admin = msg.sender;
    uint public constant TICKET_PRICE = 0.01 ether;
    uint public constant MAX_PLAYERS = 5;

    address[] public players;
    mapping(address => bool) public hasTicket;
    address public winner;

    function buyTicket() external payable {
        require(msg.sender != admin, "Admin cannot play");
        require(msg.value == TICKET_PRICE, "Wrong ticket price");
        require(!hasTicket[msg.sender], "Already bought");
        require(players.length < MAX_PLAYERS, "Tickets over");
        hasTicket[msg.sender] = true;
        players.push(msg.sender);
    }

    function declareResult() external {
        require(msg.sender == admin, "Only admin");
        require(winner == address(0), "Already declared");
        require(players.length > 0, "No players");
        uint index = uint(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, players.length)))
                     % players.length;              // only valid indexes
        winner = players[index];
    }

    function claimPrize() external {
        require(msg.sender == winner, "Not the winner");
        uint prize = address(this).balance;
        (bool ok, ) = payable(winner).call{value: prize}("");
        require(ok, "Payment failed");
    }
}
Key Takeaways
  • Modifiers encode lottery rules: admin-only actions, one ticket per address, limited tickets, single draw.
  • keccak256 over block data gives pseudo-randomness that is fine for learning but unsafe for real money.
  • Always take the modulo of the actual number of players to stay within valid indexes.
Video : Project 2 : Smart Contract for Lottery System
Resource Files
Question / Answer (15 questions)

Answer : Traditional lotteries require trust in intermediaries who sell tickets, hold funds and pick winners. A smart contract makes the rules public and executes them automatically, increasing transparency.

Answer : struct UserRecord { address useraddress; string username; uint amount; uint winningAmount; }, defined in UserRecord.sol.

Answer : checkBlockAdmin (admin only), checkTicketUser (admin cannot buy), checkTicketUserCount (one ticket per address), checkUserCount (ticket limit not exceeded) and resultStatus (result can be declared only once).

Answer : Five, because userlimit = 5 and checkUserCount requires userCount < userlimit before each purchase.

Answer : uint(keccak256(abi.encodePacked(msg.sender, block.timestamp, startvalue))) % userlimit – a hash of the caller, the block timestamp and a constant, reduced modulo 5.

Answer : It concatenates the given values into a compact byte string so they can be hashed together by keccak256.

Answer : Its inputs (caller, block timestamp, block values) are known or partly controllable, and the admin chooses when to call, so the outcome can be predicted or influenced.

Answer : Because the index is taken modulo userlimit (5) instead of the number of buyers, it may point to an unused ticket number, making the winner an empty record with the zero address.

Answer : Take the modulo of the actual number of players (userCount or players.length) and require at least one player before declaring the result.

Answer : No. TICKET_AMOUNT and totalBalance are only numbers; the purchase function is not payable and no prize is paid. The extension exercise adds real payments.

Answer : By using a verifiable random function (VRF) oracle that supplies a random number with a proof, or a commit–reveal scheme where participants commit to secrets that are revealed later.

Answer : The pull pattern lets the winner collect the prize in a separate transaction, avoiding failures or reentrancy risks during result declaration.

Answer : Because the admin controls when the result is declared; allowing the admin to play would create a conflict of interest.

Answer : That declareLotteryResult() can succeed only once; after resultDeclared is set to true, further calls revert with “Result has been Declared”.

Answer : A mapping(address => bool) hasTicket, which checks in constant time whether an address has already bought a ticket.
MCQ Test : Check Your Understanding (5 questions)

Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.

Q1. In the teaching version, the winner index is calculated modulo:

Show Answer

Correct Answer : B. userlimit (5)
The code uses % userlimit, which can select an unsold ticket.

Q2. Which function combines values into bytes before hashing?

Show Answer

Correct Answer : A. abi.encodePacked
abi.encodePacked concatenates values compactly.

Q3. Which source gives verifiable randomness to smart contracts?

Show Answer

Correct Answer : C. A VRF oracle
VRF oracles provide randomness with cryptographic proof.

Q4. Which modifier stops the same address from buying two tickets?

Show Answer

Correct Answer : D. checkTicketUserCount
It loops over buyers to find msg.sender.

Q5. To accept real Ether for a ticket, the purchase function must be:

Show Answer

Correct Answer : A. payable
Only payable functions receive Ether.

Bibliography/References

The notes, questions and MCQs on this page are original explanations written for this course. The works listed below were consulted and are cited for attribution and further reading. Product names, logos and trademarks belong to their respective owners. Short quotations, where used, are marked and attributed.

  1. Solidity Team. (n.d.). Units and Globally Available Variables. Solidity Documentation. https://docs.soliditylang.org/en/latest/units-and-global-variables.html
  2. Solidity Team. (n.d.). Security Considerations. Solidity Documentation. https://docs.soliditylang.org/en/latest/security-considerations.html
  3. Chainlink. (n.d.). Chainlink VRF (Verifiable Random Function) documentation. https://docs.chain.link/vrf
  4. ethereum.org. (n.d.). Oracles. Ethereum Developer Documentation. https://ethereum.org/en/developers/docs/oracles/
  5. Remix Project. (n.d.). Remix IDE Documentation. https://remix-ide.readthedocs.io/en/latest/
  6. Modi, R. (2018). Solidity Programming Essentials: A beginner’s guide to build smart contracts for Ethereum and blockchain. Packt Publishing. ISBN 978-1-78883-138-3.