A decentralized raffle application built with Solidity and Foundry, utilizing Chainlink VRF for provably fair randomness and automated winner selection. Contract for Creating and managing a raffle where participants enter by sending ETH, and a winner is randomly selected.
This project also leverages Chainlink VRF to ensure randomness in selecting the raffle winner. This integration provides security and fairness by preventing tampering with the random number generation process.
The project is a decentralized Raffle/Lottery system which :
- Allows Users to enter a raffle , after paying an entrance fee.
- Winners are selected at random using Chainlink VRF (Verifiable Random Function).
- A Winner selection is automated using Chainlink Automation (which automatically triggers the draw after a set time or a certain number of participants).
- The entire prize pool is automatically sent to the winner address, immediately upon drawing the winner, ensuring guaranteed payouts.
- Decentralized Entry Mechanism: Allows participants to buy tickets securely by sending the correct native cryptocurrency (e.g., ETH) or tokens to the contract, eliminating third-party processors.
- Provably Fair Randomness (Chainlink VRF): Utilizes Verifiable Random Functions (VRF) to generate a secure, transparent, and unpredictable random number to select winners, ensuring the drawing is not manipulated.
- Automated Execution (Chainlink Automation): Chainlink Automation triggers winner selection based on specific triggers at predetermined intervals. Uses automated, time-based triggers to close the raffle and initiate the winner selection process without requiring human intervention.
- Automated Prize Distribution: Automatically sends the accumulated prize pool to the winning address immediately upon drawing the winner, ensuring guaranteed payouts.
- Raffle State Management: Define distinct states, such as
OPEN,CALCULATING(drawing), andCLOSED, to prevent entries after the deadline and manage the lifecycle of the raffle. - Custom Solidity errors: gas-efficient custom errors replace
requirestring messages . - Programmatic subscription management: Foundry interaction scripts automate creating, funding, and registering the VRF subscription, so the full setup is reproducible in one command.
- Gas Optimization: Developed with smart contract best practices for gas efficiency, to reduce cost.
- Transparency & Auditability: All participants, ticket purchases, and winner selections are recorded on a public ledger, allowing anyone to verify the results.
- Event Logging: Emits events for key actions (e.g., WinnerPicked) that allow interfaces to display real-time updates to users on the blockchain.
- Intensive Testing: Includes several testing mechanism. They include: unit tests, integration tests, and fork tests.
- Blockchain Transparency: Every transaction and role assignment is recorded on the Ethereum blockchain for transparency.
- Multi-Network Support: Deployable on localhost (Anvil) and Sepolia Eth testnet. Easily deployable across different networks, using automated scripts.
- Time-Based Draws: Uses a configurable time interval between raffles.
- Security Checks: Validates conditions before performing sensitive operations.
- Solidity(^0.8.19) : The programming language for writing the Smart contracts.
- Foundry : Development framework and testing suite.
- Chainlink Automation : Decentralized, secure, and cost-efficient Web3 services network. Automated upkeep execution.
- Chainlink VRF V2.5 : Tamper-proof random number generator (RNG).
- Anvil: Local Ethereum development network used for deployment and testing.
- Forge Standard Library (forge-std): Provides testing utilities, Provides testing utilities, scripting support, and debugging tools.
- Foundry DevOps: Used to retrieve the most recently deployed contract during deployment and interaction scripts.
- Clone the repository:
git clone https://github.com/legendarycode3/raffle-smart-contract
cd raffle-smart-contract - Install dependencies:
make install
- Build the project:
make build # or forge build
- Configure your
.envfile:SEPOLIA_RPC_URL=your_sepolia_rpc_url ETHERSCAN_SEPOLIA_API_KEY=your_etherscan_api_key
- Get testnet ETH & LINK:
- ETH Sepolia Faucet: Eth Sepolia Faucet .
- Link testnet faucets: Link Testnet Faucet
- Fund Chainlink VRF Subscription:
- Visit VRF Chain Link with your SepoliaEth obtained (always use burner wallet).
- Create a subscription and note the Subscription ID.
- Fund it with testnet LINK tokens.
- Configure Makefile:
- Change account name in Makefile to the name of your desired encrypted key.
- change "--account defaultKey" to "--account <YOUR_ENCRYPTED_KEY_NAME>" (if tryintg to deploy on
sepolia testnet networkrather thananvil) - check encrypted key names stored locally with:
- change "--account defaultKey" to "--account <YOUR_ENCRYPTED_KEY_NAME>" (if tryintg to deploy on
cast wallet list- If no encrypted keys found
- Encrypt private key to be used securely within foundry:
cast wallet import <account_name> --interactive- Never commit your
.envfile. - Never use your mainnet private key for testing.
- Use a separate wallet with only testnet funds.
Compile the smart contracts:
make buildDeploy to Sepolia testnet:
make deploy-sepoliaRun all tests:
make testOr
forge testRun tests with verbosity:
make test -vvvRun specific test:
forge test --mt testFunctionNameTest coverage:
make coverageThe project includes comprehensive automated testing covering:
- Unit Tests Verify individual contract functions and expected behaviors.
- Integration Tests Validate deployment scripts, VRF subscription creation, and network configuration.
- State Transition Tests Ensure the raffle correctly transitions between
OPENandCALCULATINGstates. - Event Emission Tests Confirm important events are emitted with the expected parameters.
- Revert Condition Tests Verify the contract correctly reverts on invalid inputs and unauthorized operations.
- Winner Selection Tests Validate that Chainlink VRF correctly selects a winner, resets the raffle, and transfers the prize.
- End-to-End Flow Tests Simulate the complete raffle lifecycle from player entry through winner selection and raffle reset.
forge fmtYou can estimate how much gas things cost by running:
forge snapshot cast <subcommand> forge --help
anvil --help
cast --helpExample interactions using cast (Foundry's CLI tool):
- Enter the raffle by sending entrance fee:
cast send <RAFFLE_ADDRESS> "enterRaffle()" --value 0.01ether --rpc-url $SEPOLIA_RPC_URL --account yourEncryptedAccount
- Check raffle state (0=OPEN, 1=CALCULATING):
cast call <RAFFLE_ADDRESS> "getRaffleState()" --rpc-url $SEPOLIA_RPC_URL
- Check raffle entrance fee:
cast call <RAFFLE_ADDRESS> "getEntranceFee()" --rpc-url $SEPOLIA_RPC_URL
- Get recent winner:
cast call <RAFFLE_ADDRESS> "getRecentWinner()" --rpc-url $SEPOLIA_RPC_URL
- Check if automation should trigger:
cast call <RAFFLE_ADDRESS> "checkUpkeep(0x)" --rpc-url $SEPOLIA_RPC_URL
- Get any player on a specific index:
cast call <RAFFLE_ADDRESS> "getPlayer(0)" --rpc-url $SEPOLIA_RPC_URL
Or Interact with Raffle contract deployed on sepolia testnet:
Directly (from Sepolia Etherscan
- Entry Raffle(Phase): Players can send the required entrance fee to the contract to join the current raffle round, by calling
enterRaffle(). The players addresses are stored in an array. What happens here:- Checks if a player entry fee is enough.
- Stores player address.
- Adds ETH to contract balance.
- Upkeep Check(Automated): Chainlink Automation detects that if the raffle is ready to pick a winner or not, based on the following checks:
- Raffle entry is in
OPENstate. - Enough time has passed.
- Contract holds sufficient ETH(atleast 1 to 2 players).
- Subscription funded(LINK deposit successful).
- Raffle entry is in
- Winner Selection: When conditions are satisfied, the function
performUpkeep()is called:- Raffle status updated to
CALCULATING. - Random number request initiated (from Chainlink VRF).
- Raffle status updated to
- Fulfillment: The Chainlink VRF automatically calls the
fulfillRandomWords():- Random number is received
- Random winner calculated via modulo.
- Prize awarded to winner and transfered.
- Raffle Resets for Next Round:
After payout:- Players array is cleared.
- Timestamp is reset.
- State goes back to OPEN.
Constructor Parameters:
| Parameter | Description |
|---|---|
_subscriptionId |
Chainlink VRF v2.5 subscription ID |
gasLane |
KeyHash to define the max gas price for VRF |
interval |
Time (in seconds) between raffle draws |
enteranceFee |
Amount of ETH required to enter the raffle |
callbackGasLimit |
Max gas to use for fulfillRandomWords() callback |
vrfCoordinator |
Address of the Chainlink VRF coordinator for your network |
constructor(...): Initializes the raffle configuration and Chainlink VRF setup. Sets theentrance fee,raffle interval,VRF coordinator details,subscription ID,callback gas limit,initializes the timestamp, and opens the raffle.enterRaffle(): Enter the raffle after paying the entrance feecheckUpkeep(): Ensures to Check if raffle conditions are met for winner selectionperformUpkeep(): Initiate / Triggers the winner selection processfulfillRandomWords(): Randomness request fulfillment function. Callback function for Chainlink VRF
getEntranceFee(): Returns the minimum ETH required to enter the raffle.getRaffleState(): Returns the current state of the raffle (e.g., OPEN, CALCULATING).getPlayer(uint256): Returns the address of a player given their index.getLastTimeStamp(): Returns the timestamp of the last recorded action.getRecentWinner(): Returns the address of the winner from the previous round.
REQUEST_CONFIRMATIONS: How many confirmations the Chainlink node should wait before responding.NUM_WORDS: The number of words/slots to request from the oracle.i_entranceFee: The minimum amount of ETH required to enter the raffle.i_interval: Time interval set at deployment (e.g., in seconds).i_keyHash: The key hash for the Chainlink VRF Coordinator, identifying the gas lane.i_subscriptionId: Subscription ID assigned by Chainlink/external service at deploymen.i_callbackGasLimit: The maximum gas allowed for the callback function (e.g., fulfillRandomWords).s_players: Array of players allowed to receive ETH, managed privately (e.g Array of participants).s_recentWinner: Address of the last winner.
DeployRaffle.s.sol: Deployment script. The deployment script is an automation file that pushes the compiled smart contract to the blockchain and sets up its initial configurations.HelperConfig.sol: Network configuration management. The HelperConfig script (typically named HelperConfig.s.sol) is the utility script used to manage and provide deployment parameters (E.G such as RPC URLs, Chainlink oracle addresses, and initial fees) based on the specific blockchain network you are targeting (e.g. like Sepolia, local Anvil node).RaffleTest.t.sol: Test suite.
Create a .env file with the following variables:
SEPOLIA_RPC_URL=your_sepolia_rpc_url
ETHERSCAN_SEPOLIA_API_KEY=your_etherscan_api_key├── script # deployment, configuration, and interaction scripts directory
│ ├── DeployRaffle.s.sol # Main deployment script
│ ├── HelperConfig.s.sol # Network configuration script
│ └── Interaction.s.sol # Chainlink interaction scripts (VRF subscription management)
├── src # Smart contracts directory
│ └── Raffle.sol # Smart contracts main source code(raffle contract logic)
└── test # Test files directory
│ ├── integration
│ │ └── IntegrationTest.t.sol # Integration tests
│ ├── mocks # development/testing contract directory
│ │ └── LinkToken.sol # dummy testing contract designed to simulate the real Chainlink LINK token on a local blockchain when test with mock
│ └── unit
│ └── RaffleTest.t.sol # Unit tests
├── lib
│ ├── forge-std/ # Foundry standard library
│ ├── chainlink-brownie-contracts/ # Chainlink contracts
│ ├── foundry-devops/ # DevOps utilities
│ └── solmate/ # Optimized utilities
├── foundry.toml # Foundry configuration
├── Makefile # Build commands
└── README.md # This entire text documents file
The main raffle contract implementation:
- Entry raffle fee payment.
- Player registration for the raffle commencement.
- Integration with Chainlink VRF to activate random winner selection.
- Integration with Chainlink Automation for automated, decentralized , periodic draws.
- Raffle Winner Prize distribution.
- VRF (Verifiable Random Function): Provides cryptographically secure randomness.
- Automation: Enables trustless execution of raffle draws based on time intervals.
The project includes intensive tests covering:
- Contract deployment.
- Raffle entry flow mechanics.
- Chainlink VRF integration (for randomness).
- Automation functionality.
- Edge cases and error handling.
Run tests with different verbosity levels:
forge test # Standard output
forge test -v # Verbose
forge test -vv # More verbose
forge test -vvv # Very verbose
forge test -vvvv # Maximum verbosity- Entrance Fee:
0.001 ETH - Interval:
30 seconds - VRF Coordinator:
0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B - Gas Lane:
0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae - Callback Gas Limit:
500,000
- RPC URL:
http://localhost:8545 - Chain ID:
31337 - VRF Mock:
VRFCoordinatorV2_5Mock - Entrance Fee:
0.001 ETH - Interval:
30 seconds
NOTE:
- Uses
VRFCoordinatorV2_5Mockfor local testing. - Anvil provides pre-funded test accounts by default.
Raffle__SendMoreEThToEnterRaffle()- Insufficient entrance fee- `Raffle__TransferFailed() - ETH transfer to winner failed
Raffle__RaffleNotOpen()- Raffle is calculating winner (calculating State)Raffle__UpkeepNotNeeded()- Conditions not met for upkeep.
- Immutable Variables: Frequently accessed configuration values such as the entrance fee, raffle interval, VRF key hash, subscription ID, and callback gas limit are declared as
immutable, reducing storage reads after deployment. - Constant Variables: Fixed values like
REQUEST_CONFIRMATIONSand NUM_WORDS are declared asconstant, eliminating storage costs by embedding their values directly into the contract bytecode. - Custom Errors: Uses custom errors instead of revert strings, reducing deployment size and lowering gas costs whenever transactions revert.
- Efficient Array Reset: The player array is reset using
new address payable[](0), efficiently clearing the participant list for the next raffle round. - Optimized Storage Access: Frequently used configuration values are stored as
immutable, while dynamic state variables are updated only when necessary to minimize expensive storage operations. - Efficient State Management: The contract updates only the required state variables during each raffle cycle, avoiding unnecessary storage writes and reducing execution costs.
A Makefile is included to streamline commands for cleaning, building, testing, updating, formatting, deployment, and more. You can use it to execute tasks without needing to remember specific commands. Just run the command you need like this:
make <command>- State Locking During Winner Selection: The raffle state is changed to
CALCULATINGbefore requesting randomness, preventing new players from entering while a winner is being determined. - Secure and Verifiable Randomness: Uses Chainlink VRF for cryptographically secure randomness. All raffle randomness is generated using Chainlink VRF v2.5, which provides cryptographically secure and tamper-resistant randomness for fair winner selection.
- Checks-Effects-Interactions Pattern: Executes checks-effects-interactions smart contract pattern.
- Comprehensive Test Coverage: The project includes extensive unit and integration tests covering edge cases, state transitions, event emissions, revert conditions, and other raffle execution flow.
- Immutable Configuration Variables: Critical configuration values are stored as immutable variables, preventing modification after deployment. Immutable variables are used for gas optimization.
- Custom Error Handling: Custom Errors which reduce gas costs and provide clear revert reasons.
- ETH Transfer Validation: The contract validates the success of the ETH transfer to the winner and reverts the transaction if the transfer fails, preventing silent loss of funds.
- Sufficient Balance Requirement: The raffle only proceeds when the contract has a positive ETH balance, ensuring there are funds available for prize distribution.
- Professional Audit Recommendation: Although the contract includes several security best practices, a professional smart contract audit is recommended before deploying to mainnet or handling significant funds.
- Professional security audit.
- Bug bounty program.
- The Chainlink VRF subscription must remain funded.
- The Chainlink VRF subscription must remain adequately funded insufficient subscription funding may prevent randomness requests from being fulfilled, potentially leaving the raffle in the
CALCULATINGstate. - Raffle may become stuck in
CALCULATINGstate if Chainlink VRF does not fulfill the randomness request after performUpkeep() is called. - The VRF Coordinator address must be correct for the target network.
- Callback gas limit must be sufficient for
fulfillRandomWords()to execute winner selection, state reset, event emission, and ETH transfer successfully. - Winner transfer may revert if the selected winner is a contract that rejects ETH transfers, causing the callback transaction to fail.
- Callback gas limit must be sufficient for winner selection and prize transfer.
- Chainlink Automation execution may be delayed due to keeper availability, network congestion, or gas price conditions.
- If the winner is a contract with a failing fallback function, the transfer may revert.
- Correct VRF subscription setup is required, including funding the subscription and registering the Raffle contract as an authorized consumer.
- Immutable configuration values such as entrance fee, interval, gas lane, subscription ID, and callback gas limit cannot be changed after deployment.
- Fund the Chainlink VRF subscription before deploying to a live network.
- Monitor the subscription balance regularly.
- Test thoroughly on Sepolia before deploying to mainnet.
- Use a secure private key management solution.
- Consider adding emergency withdrawal or pause functionality for production deployments.
- Perform a professional security audit before handling significant funds.
- LinkedIn: @legendarycode3
- Twitter: @legendary_code_
- Github: @legendarycode3
Finally, Feel free to explore and improve the project. Contributions, issues, and feature requests are welcome! ❤️
This project demonstrates how smart contracts on the Ethereum blockchain can enable decentralized and trustless raffle systems. The transparency and immutability of the blockchain ensure fairness through the blockchain technology.
Built with ❤️ using Foundry and Chainlink