S-20200201

Security Intermediate February 1, 2020

Back to All Tasks

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.