Introduction
During audits of various smart contracts, I encountered a fascinating category of issues. These problems arise not only from developer oversight but also from inherent design flaws in smart contracts themselves. Without awareness of these pitfalls, developers risk introducing vulnerabilities.
This report explores two critical design flaws identified in Ethereum smart contracts:
- Race Conditions
- Loop-Related Denial-of-Service (DoS) Vulnerabilities
Vulnerability Details
1. Race Conditions
Background
On November 29, 2016, researchers Mikhail Vladimirov and Dmitry Khovratovich highlighted a critical issue in the ERC20 standard through their paper "ERC20 API: An Attack Vector on Approve/TransferFrom Methods". The core problem involves approve/transferFrom race conditions.
Attack Scenario
- Step 1: User A approves User B to spend 100 tokens.
- Step 2: User A attempts to reduce the allowance to 50 tokens.
- Step 3: User B front-runs the reduction by submitting a higher-Gas transaction to spend 100 tokens before the approval update is processed.
- Result: User B successfully spends 150 tokens (100 + 50), exploiting the race condition.
Root Cause
- Ethereum transactions aren’t finalized until included in a block.
- Miners prioritize transactions with higher
gasPrice, enabling malicious actors to manipulate transaction ordering.
Vulnerable Code Example
function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}2. Loop-Related DoS Issues
Gas Consumption Pitfalls
- Ethereum imposes gas limits per block. Excessively complex loops may exhaust gas, causing transaction failures.
- Example: A payout function iterating through a long payee list could fail if it exceeds gas limits.
Malicious Exploitation
- Attackers can manipulate loop iterations (e.g., by inflating an address list) to trigger DoS.
- Historical Incident: The GovernMental contract trapped 1,100 ETH due to an unprocessable address list.
Vulnerable Code Example
function distribute(address[] addresses) onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
// Processing logic
}
}Impact Analysis
Our scans of 39,548 public contracts revealed:
- 22,981 had race condition vulnerabilities.
- 1,810 exhibited loop-related DoS risks.
👉 Explore real-time contract audits for deeper insights.
Mitigation Strategies
Race Conditions
Implement a "zero-first" approval pattern:
require((_value == 0) || (allowance[msg.sender][_spender] == 0));Loop DoS
- Avoid user-controlled loop depths.
Use withdrawal patterns to shift gas costs to users.
function distribute(address[] addresses) onlyOwner { for (uint i = 0; i < addresses.length; i++) { if (!address_claimed_tokens[addresses[i]]) { // Transfer logic } } }
FAQs
Q: How can I detect race conditions in my contract?
A: Use tools like Haotian to scan for unprotected approve functions.
Q: Are loops always unsafe in smart contracts?
A: No—but they require careful gas management and input validation.
Q: Can race conditions be fully prevented?
A: While hard to eliminate entirely, mitigation patterns reduce risks significantly.