Overview
The Lottery example demonstrates how to implement a simple lottery system using cryptographically secure randomness, time-based constraints, and on-chain state management. This is the simplest of the three main examples, making it perfect for learning finalizers.This example is located at
.circleci/lottery/ in the Leo repository and is actively tested in CI.Program Structure
Data Structures
Mapping: Winner Count
Tracks the number of winners on-chain:- Key:
u8(always0u8in this example) - Value:
u8(number of winners so far)
Record: Ticket
Represents a lottery ticket:- Private by default (encrypted on-chain)
- Only contains the owner’s address
- Returned to the player when they play
The ticket itself doesn’t indicate whether it’s a winning ticket. The winner is determined during finalization.
Core Function
Playing the Lottery
- Creates a new ticket record
- Sets the owner to
self.caller(the transaction initiator) - Returns the ticket and a finalizer
Ticket: The lottery ticket (private record)Final: Deferred computation that executes on-chain
Finalizer Logic
Step-by-Step Breakdown
1. Expiration Check
block.heightis the current block number- If the current block is beyond 1000, the assertion fails
- The transaction is rejected
block.height is only available in finalizer functions (not in regular functions). It represents the blockchain’s current block number.
In production, you’d set this to a reasonable future block height. Block 1000 is just for demonstration.
2. Random Winner Selection
ChaCha::rand_bool()generates a random boolean (true/false)- If
true: The ticket wins (assertion passes) - If
false: The ticket loses (assertion fails, transaction rejected)
- Deterministic: Same seed always produces same result
- Unpredictable: Seed is derived from blockchain state
- Verifiable: Anyone can verify the randomness was fair
3. Winner Limit Check
num_winners.get_or_use(0u8, 0u8)reads the current winner count- If the key doesn’t exist, returns
0u8(default) - Asserts that we have fewer than 5 winners
- If we already have 5 winners, the assertion fails
4. Increment Winner Count
- Adds 1 to the winner count
- Stores the updated count on-chain
- This change is permanent and visible to all future transactions
Execution Flow
Here’s what happens when someone plays:Running the Example
Build the Program
Play the Lottery
-
Success (Winner!):
-
Failure (Not a winner):
-
Failure (Lottery expired):
-
Failure (Lottery full):
Use the Demo Script
Key Concepts
Finalizers vs Regular Functions
| Feature | Regular Function | Finalizer |
|---|---|---|
| Execution | During proof generation (off-chain) | After proof verification (on-chain) |
| Access to mappings | No | Yes (read and write) |
Access to block.height | No | Yes |
| Can fail after proof? | No | Yes (via assertions) |
| Visibility | Private computation | Public execution |
Why Use a Finalizer?
In this example, the finalizer is essential because:- Randomness:
ChaCha::rand_bool()must be deterministic during proving - State: Reading and writing the winner count requires on-chain access
- Time: Checking
block.heightrequires on-chain context
Transaction Failures
This means:- Players who lose don’t get a ticket
- Players pay gas fees even if they lose
- The winner count only increments for actual winners
Customization Ideas
Adjust Winning Odds
Change Expiration
Multiple Prize Tiers
Paid Entry Fee
Combine with the token example:Testing
Create Test Inputs
Createinputs/lottery.in:
Run Tests
Because the lottery uses randomness, tests may pass or fail randomly. In production tests, you’d want deterministic behavior.
Security Considerations
Randomness Manipulation
Implications:- Players cannot manipulate randomness
- Validators cannot manipulate randomness
- Everyone can verify the randomness was fair
Block Height Gaming
Players could potentially wait for a specific block height if they believe it affects their odds. In this example, it doesn’t matter since randomness is based on the transaction seed, not block height.Winner Limit
The 5-winner limit is arbitrary. In production:- Consider time-based resets
- Or periodic “rounds” with separate mappings
- Or dynamic limits based on entry fees
Related Examples
Token
Add paid entry fees
Tic-Tac-Toe
More complex state management
Further Reading
Finalize
Deep dive into finalize blocks
Mappings
On-chain storage guide
Records
Private data structures
Standard Library
ChaCha RNG and more