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.
- 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.