Skip to content
Last updated: March 11, 2025

SCWE-075: Incorrect Ether Balance Tracking

Stable Version v0.0.1

This content is in the version-(v0.0.1) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Relationships

Description

Incorrect Ether balance tracking occurs when a contract manually maintains an internal balance variable instead of relying on address(this).balance. This creates inconsistencies when Ether is received outside of expected functions (e.g., via selfdestruct(), transfer(), or direct deposits).

Attackers can exploit this by artificially inflating the contract's perceived balance, leading to unauthorized withdrawals or failed transactions. This issue is common in poorly designed deposit/withdraw systems that do not properly verify the actual contract balance.

Attack Scenario

An attacker sends Ether to the contract using selfdestruct(), increasing its actual balance without updating the internal tracking variable. Later, a user tries to withdraw funds, but the contract incorrectly assumes it has more Ether than it actually does, causing unexpected failures or exploits.

Remediation

  • Use address(this).balance instead of manually tracking Ether balance.
  • Prevent external Ether deposits by disabling the fallback function unless explicitly needed.
  • Ensure proper accounting by always reconciling balances before allowing withdrawals.

Vulnerable Contract Example

// ❌ Vulnerable to incorrect balance tracking due to external Ether deposits
contract IncorrectBalanceTracking {
    uint public balance;  // ❌ Manually tracking Ether balance

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint _amount) public {
        require(balance >= _amount, "Insufficient funds");
        payable(msg.sender).transfer(_amount);
        balance -= _amount;
    }
}
Why is this vulnerable? - The contract does not account for direct Ether transfers outside deposit(). - An attacker can send Ether via selfdestruct(), inflating the contract balance without updating balance. - This can lead to withdrawals being blocked or excessive withdrawals.

Fixed Contract Example

// ✅ Secure implementation that tracks actual balance correctly
contract CorrectBalanceTracking {
    function deposit() public payable {}

    function withdraw(uint _amount) public {
        require(address(this).balance >= _amount, "Insufficient funds");  // ✅ Correct balance check
        payable(msg.sender).transfer(_amount);
    }

    // Optional: Prevent direct Ether transfers
    receive() external payable {
        revert("Direct deposits not allowed");
    }
}
Why is this secure? - Uses address(this).balnce for accurate balance tracking. - Prevents external deposits unless explicitly intended. - No manual balance variable, reducing risk of inconsistencies.