Skip to content

Repository files navigation

Raffle (Decentralized Lottery) Smart Contract

This is a smart contract I built while learning Solidity and Foundry. It's a simple lottery/raffle where people pay an entrance fee, and after some time passes, a winner is picked completely at random and gets the whole pot. No admin picks the winner, no one can cheat the randomness — it's all handled on-chain using Chainlink.

I built this as a way to actually understand how Chainlink VRF (random numbers) and Chainlink Automation (automatic function calls) work together in a real contract, instead of just reading about them.

How it works

  1. Anyone can enter the raffle by calling enterRaffle() and sending at least the entrance fee.
  2. Every X seconds (set when the contract is deployed), Chainlink Automation checks if it's time to pick a winner.
  3. If it is time (and there's at least 1 player and some ETH in the pot), Automation calls performUpkeep(), which asks Chainlink VRF for a random number.
  4. Once Chainlink VRF sends back the random number, the contract picks a winner from the list of players and sends them the entire balance.
  5. The raffle resets and opens back up for a new round.

Nobody has to manually trigger anything — that's the whole point of using Automation. And the randomness can't be manipulated by miners/validators or the owner, because it comes from Chainlink VRF instead of something like block.timestamp.

A couple of design decisions

  • Single winner takes the whole pot. No splitting it up, keeps things simple.
  • The entrance fee can be changed by the owner, but only for people entering after the change. If you already entered at the old price, you're not affected by a later fee update.
  • There's no minimum number of players. Even if only 1 person has entered when the timer runs out, they can still "win" (basically just getting their own entry fee back, minus gas). It's a bit of a weird edge case but not a bug.

Tech stack

  • Solidity ^0.8.19
  • Foundry for building, testing, and deploying
  • Chainlink VRF v2.5 for verifiable randomness
  • Chainlink Automation for the time-based upkeep trigger

Project structure

src/
  Raffle.sol                  <- the actual contract

script/
  DeployRaffle.s.sol           <- deploys everything (subscription, funding, the contract itself)
  HelperConfig.s.sol           <- figures out which network we're on and gives the right config
  Interactions.s.sol           <- scripts for creating/funding a VRF subscription and adding a consumer

test/
  unit/RaffleTest.t.sol        <- fast tests using local mocks (Anvil)
  staging/RaffleStagingTest.t.sol  <- tests that run against a real deployed contract (e.g. Sepolia)
  mocks/LinkToken.sol          <- fake LINK token, only used for local testing

Running it locally

Clone the repo and install dependencies:

forge install

Run the unit tests (uses local Anvil + Chainlink mocks, no real network needed):

forge test

Deploy to a local Anvil chain:

anvil

then in another terminal:

forge script script/DeployRaffle.s.sol --rpc-url http://127.0.0.1:8545 --broadcast

Deploying to Sepolia

You'll need:

  • A Sepolia RPC URL (Alchemy/Infura both work)
  • A funded testnet wallet
  • Some testnet LINK for the VRF subscription
forge script script/DeployRaffle.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify

The deploy script handles creating and funding the VRF subscription automatically if one doesn't already exist, and registers the contract as a consumer for you.

After deploying, you can run the staging tests against the live contract:

forge test --match-contract RaffleStagingTest --fork-url $SEPOLIA_RPC_URL

What I learned building this

  • Why you should never use block.timestamp or block.difficulty for randomness (miners/validators can influence them)
  • The difference between requesting randomness and receiving it — VRF is a two-step process (request, then callback), not instant
  • Checks-Effects-Interactions and why the order of operations in fulfillRandomWords actually matters for security, not just style
  • How Chainlink Automation decides when to call your contract (checkUpkeep / performUpkeep)
  • Why local mock testing and "real network" staging testing are two genuinely different things, not just the same tests run twice

Things I'd probably improve later

  • Add a minimum player count option instead of always allowing single-player rounds
  • Look into splitting the prize among top N winners instead of a single winner
  • Add better logging/events for the frontend to hook into
  • Maybe support ERC20 entrance fees instead of just ETH (once I actually learn ERC20 properly)

Built while learning Solidity/Foundry

About

A decentralized single-winner lottery smart contract built with Foundry, using Chainlink VRF v2.5 for verifiable randomness and Chainlink Automation for trustless upkeep.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages