S-20200225

Security Intermediate February 25, 2020

Back to All Tasks

Problem Statement

You are tasked with auditing a simple Ether Wallet smart contract written in Solidity. Your goal is to identify potential security flaws, particularly focusing on reentrancy attacks, and propose solutions to mitigate these risks. The contract allows users to deposit and withdraw Ether. Your task includes:
1. Analyzing the provided Solidity code.
2. Identifying any vulnerabilities that could lead to unauthorized access or loss of funds.
3. Suggesting modifications to enhance security and prevent reentrancy attacks.
4. Writing a short report (up to 500 words) detailing your findings, analysis, and proposed solutions.

Concepts

  • reentrancy attack
  • smart contract vulnerabilities
  • solidity programming

Constraints

  • Use Solidity version 0.8.x for rewriting the contract.
  • Do not introduce any new features beyond those required to secure the contract against reentrancy attacks.

Security Notes

  • Reentrancy attacks can drain funds from a vulnerable smart contract by repeatedly calling functions before previous calls are completed.
  • Always ensure that external function calls are made after updating state variables to prevent attackers from manipulating the contract's state.

Solutions

Java Solution

pragma solidity ^0.8.0;

// EtherWallet contract that includes protection against reentrancy attacks.
contract EtherWallet {
    // Mapping to store the balance of each user
    mapping(address => uint256) public balances;

    // Event to log deposits
    event Deposit(address indexed sender, uint256 amount);

    // Event to log withdrawals
    event Withdrawal(address indexed recipient, uint256 amount);

    // Function to deposit Ether into the wallet
    function deposit() external payable {
        require(msg.value > 0, "Deposit amount must be greater than zero");
        balances[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    // Function to withdraw Ether from the wallet
    function withdraw(uint256 _amount) external {
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        // Update state variable before making external call to prevent reentrancy attack
        balances[msg.sender] -= _amount;

        // Use transfer instead of send as it forwards a fixed amount of gas and throws on failure
        payable(msg.sender).transfer(_amount);
        emit Withdrawal(msg.sender, _amount);
    }
}

This Solidity contract represents an Ether Wallet with protection against reentrancy attacks. The contract includes two main functions: deposit() and withdraw().
The deposit() function allows users to add Ether to their balance in the wallet. It checks if the deposited amount is greater than zero, updates the user's balance, and emits a Deposit event.
The withdraw() function enables users to retrieve Ether from their balance. It first ensures that the user has sufficient funds for the withdrawal. Importantly, it reduces the user's balance before making an external call to transfer the Ether. This ordering of operations is crucial as it prevents reentrancy attacks by ensuring the state of the contract is updated before any potentially malicious function calls can be made.
The use of payable(msg.sender).transfer(_amount) ensures that a fixed amount of gas (2300) is sent with the transaction, which is not enough to execute additional code in the recipient's fallback or receive function, thus mitigating reentrancy risks. If more control over gas usage is needed, payable(msg.sender).call{value: _amount}() could be used instead, but it requires careful handling to ensure security.
This contract adheres to best practices by following the checks-effects-interactions pattern, where state variables are updated before any external calls are made. This simple yet effective strategy helps prevent attackers from exploiting vulnerabilities such as reentrancy attacks.