Problem Statement
You are tasked with securing a simple Ethereum smart contract designed for a decentralized lottery system. The current implementation is vulnerable to reentrancy attacks, which can allow attackers to drain the contract's funds. Your job is to identify and fix this vulnerability by modifying the provided code snippet. Additionally, ensure that your solution adheres to best practices in Solidity programming to prevent similar issues in the future.
Concepts
- reentrancy attack
- smart contract vulnerabilities
- solidity best practices
Constraints
- Use Solidity version ^0.8.0
- Do not use low-level calls unless absolutely necessary
- Preserve the original functionality of the lottery system
Security Notes
- Always validate inputs and handle potential overflows or underflows
- Consider using checks-effects-interactions pattern to mitigate reentrancy risks
- Regularly audit your code for common security vulnerabilities
Solutions
Python Solution
pragma solidity ^0.8.0;
contract SecureLottery {
address public owner;
uint256 public ticketPrice;
address[] public players;
// Event to log when a player wins the lottery
event WinnerDeclared(address winner, uint256 prize);
constructor(uint256 _ticketPrice) {
owner = msg.sender;
ticketPrice = _ticketPrice;
}
// Function for players to buy tickets
function buyTicket() external payable {
require(msg.value == ticketPrice, "Incorrect amount sent");
players.push(msg.sender);
}
// Internal function to pick a winner and distribute the prize
function _pickWinnerAndDistributePrize() internal {
require(players.length > 0, "No players have joined the lottery");
// Randomly select an index from players array
uint256 winnerIndex = uint256(blockhash(block.number - 1)) % players.length;
address payable winner = payable(players[winnerIndex]);
// Emit event before transferring funds to prevent reentrancy
emit WinnerDeclared(winner, address(this).balance);
// Transfer the contract balance to the winner
winner.transfer(address(this).balance);
// Reset players array after prize distribution
players = new address[](0);
}
// Function for owner to pick a winner and distribute prize
function pickWinnerAndDistributePrize() external {
require(msg.sender == owner, "Only the contract owner can call this function");
_pickWinnerAndDistributePrize();
}
} The provided Solidity code defines a simple and secure decentralized lottery system. The contract uses best practices to avoid reentrancy attacks, which are common vulnerabilities in smart contracts that allow attackers to repeatedly call functions and drain the contract's funds.
The contract is designed with an owner who can control when the lottery is drawn and the prize distributed. Players buy tickets by sending a specified amount of Ether (ticketPrice) to the contract address, which gets added to the players array.
To prevent reentrancy attacks, the code follows the checks-effects-interactions pattern:
1. Checks: The function _pickWinnerAndDistributePrize first ensures that there are players in the lottery and that the owner is calling the function.
2. Effects: Before transferring funds, an event WinnerDeclared is emitted to record the winner and prize amount. This step prevents any interactions with external contracts.
3. Interactions: After emitting the event, the contract transfers all its Ether balance to the randomly selected winner and resets the players array to prepare for the next lottery round.
By adhering to this pattern and avoiding direct calls to untrusted contracts, the code mitigates reentrancy risks while preserving the original functionality of the lottery system.