Race Condition
A vulnerability where the outcome depends on the timing or ordering of events, allowing attackers to exploit the gap between operations.
A race condition is a security vulnerability where the behavior of a system depends on the sequence or timing of uncontrollable events. In smart contracts, race conditions typically occur when multiple transactions compete to modify the same state, and attackers can exploit the window between related operations. The most notorious example is the ERC20 allowance race condition, where changing an approval amount can be exploited through front-running.
The Classic ERC20 Allowance Race Condition
The standard ERC20 approve function sets an allowance to a specific value:
1function approve(address spender, uint256 amount) public returns (bool) {2 _allowances[msg.sender][spender] = amount;3 return true;4}
This creates a race condition when users try to change an existing allowance. Consider this scenario:
- Alice previously approved Bob to spend 100 tokens
- Alice decides to change the approval to 50 tokens
- Alice submits an
approve(Bob, 50)transaction - Bob sees this pending transaction in the mempool
- Bob quickly submits
transferFrom(Alice, Bob, 100)with higher gas - Bob's transaction executes first, taking 100 tokens
- Alice's approval transaction executes, setting allowance to 50
- Bob can now take another 50 tokens using the new allowance
Alice intended to reduce Bob's access from 100 to 50 tokens, but Bob extracted 150 tokens total. The race condition exists in the gap between Alice's intent and the transaction's execution.
Race Conditions Beyond Allowances
Race conditions appear in many smart contract contexts:
Price oracle updates: If a contract reads a price, makes a decision, then executes based on that price, attackers may manipulate the price between read and execution.
Multi-step operations: Any operation split across multiple transactions creates race condition opportunities. An attacker may insert transactions between steps.
Governance voting: Proposal execution that depends on token balances at execution time can be manipulated by acquiring tokens just before execution.
Auction settlements: Between bid submission and settlement, conditions may change in ways that invalidate the original bid's assumptions.
Preventing Race Conditions
Several patterns help prevent or mitigate race conditions:
Increase/Decrease Allowance: Instead of setting absolute values, change allowances incrementally:
1function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {2 _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);3 return true;4}56function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {7 uint256 currentAllowance = _allowances[msg.sender][spender];8 require(currentAllowance >= subtractedValue, "Decreased allowance below zero");9 _approve(msg.sender, spender, currentAllowance - subtractedValue);10 return true;11}
Set to zero first: Require allowance to be zero before setting a new non-zero value. This forces a two-transaction process but eliminates the race.
Permit (EIP-2612): Allow users to approve via signature rather than transaction, combining approval and transfer into a single atomic operation.
Commit-reveal schemes: Split operations into commitment and reveal phases. Commitments are hidden; reveals execute after commitment confirmation.
Atomic operations: Combine related operations into single transactions that cannot be interrupted.
Race Conditions and Reentrancy
Reentrancy attacks are a special case of race condition where the attacker re-enters a function before its state updates complete. The checks-effects-interactions pattern prevents this by ensuring state changes happen before external calls.
While reentrancy occurs within a single transaction's execution, classic race conditions occur across multiple transactions. Both exploit timing gaps between operations.
Testing for Race Conditions
Identifying race conditions requires thinking adversarially about transaction ordering:
Trace operation sequences: Map out all multi-step operations and consider what happens if transactions are reordered or interleaved.
Simulate front-running: In tests, insert attacker transactions between user operations to verify the protocol handles interleaving safely.
Review state dependencies: Any function that reads state, performs logic, then modifies state may be vulnerable if external actors can change the read state.
Check mempool exposure: Consider what information pending transactions reveal and how attackers might exploit that visibility.
Race Conditions in Audit Reports
Race conditions are commonly reported as medium to high severity findings depending on exploitability and impact. Auditors specifically examine:
- Allowance handling in ERC20 and similar tokens
- Multi-transaction workflows in governance and staking
- Price-dependent operations that span multiple blocks
- Any state that can change between check and use
Understanding race conditions is essential for both smart contract developers and security auditors. The atomic nature of individual transactions doesn't protect against races that span multiple transactions, making careful design critical for secure protocols.
Articles Using This Term
Learn more about Race Condition in these articles:
Related Terms
Front-running
The practice of observing pending transactions and submitting similar transactions with higher gas fees to execute first, extracting value.
Reentrancy Attack
A vulnerability where external calls allow malicious contracts to recursively call back before state updates complete.
Allowance
An ERC20 mechanism that permits a third party to spend tokens on behalf of the token owner, up to a specified limit.
Mempool
The memory pool where pending transactions wait before being included in a block, visible to all network participants.
Need expert guidance on Race Condition?
Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.
Get a Quote

