Non-Reverting Functions
id: LS41M
title: Non-Reverting Functions
baseSeverity: M
category: error-handling
language: solidity
blockchain: [ethereum, polygon, bsc, arbitrum, optimism]
impact: Unreliable execution, user fund loss, logic inconsistency
status: draft
complexity: low
attack_vector: external
mitigation_difficulty: easy
versions: [">=0.6.0", "<=0.8.25"]
cwe: CWE-252
swc: SWC-123
π Description
- According to the ERC-20 standard, the approve() function should return a boolean indicating success. However, many real-world ERC-20 tokens are non-standard or broken, and either:
- Do not return a boolean at all
- Return garbage data or nothing
- Do not revert on failure, making it hard for calling contracts to detect errors
- If a contract interacts with such broken tokens assuming a valid true/false return, it may:
- Assume the approval succeeded when it did not
- Proceed with transfers using stale allowances
- Allow spoofed approval logic, where malicious tokens always return true
π¨ Vulnerable Code
pragma solidity ^0.8.0;
interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
}
contract VulnerableVault {
function approveToken(address token, address spender, uint256 amount) external {
bool success = IERC20(token).approve(spender, amount); // β assumes ERC20 compliance
require(success, "Approval failed");
}
}
π§ͺ Exploit Scenario
- Alice uses a contract to interact with a non-standard token (e.g., USDT).
- The tokenβs approve() does not return anything (missing returns (bool)).
- The contract checks the return value and assumes the approval failed (false).
- Transaction reverts, blocking functionality.
- In some cases, attacker-controlled tokens could always return true, bypassing allowance checks in wrapped logic.
Assumptions:
- The token is non-standard and behaves differently than OpenZeppelinβs ERC20
- The dApp or vault assumes approve() returns a bool and is compliant
β Fixed Code
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract SafeVault {
using SafeERC20 for IERC20;
function approveToken(address token, address spender, uint256 amount) external {
IERC20(token).safeApprove(spender, amount); // β
handles non-standard tokens
}
}
π§ Contextual Severity
- context: "Financial logic relying on unchecked return values"
severity: M
reasoning: "Can lead to partial failures, fund loss, and broken state"
- context: "Low-risk view or logging function"
severity: I
reasoning: "No financial or state impact"
- context: "Function fully protected with `require()` and SafeERC20"
severity: I
reasoning: "Vulnerability mitigated"
π‘οΈ Prevention
Primary Defenses
- Always use SafeERC20.safeApprove(), safeTransfer(), and safeTransferFrom()
- Reject interacting with known broken tokens unless explicitly handled
Additional Safeguards
- Maintain allowlists/deny-lists for known broken ERC20 tokens
- Detect and log any token interaction inconsistencies
Detection Methods
- Check for interfaces that assume approve() returns a bool
- Tools: Slither (erc20-interface-violations), MythX, ConsenSys Diligence linter
π°οΈ Historical Exploits
- Name: OMG Token Transfer Compatibility Failures
- Date: 2021-07
- Loss: ~$80,000
- Post-mortem: Link to post-mortem
π Further Reading
β Vulnerability Report
id: LS41M
title: Non-Reverting Functions
severity: M
score:
impact: 4
exploitability: 3
reachability: 4
complexity: 2
detectability: 5
finalScore: 3.75
π Justifications & Analysis
- Impact: Can cause critical logic assumptions to fail (e.g., spend limits, access denial)
- Exploitability: Easily triggered with common non-compliant tokens like USDT or custom malicious tokens
- Reachability: Extremely common in any contract integrating ERC20s
- Complexity: Relies on understanding quirks in ERC20 implementations
- Detectability: Well-known and detectable through static analysis or testing