In Solidity, failed external calls don't always revert the transaction. Some return false silently. If your contract doesn't check that return value, it continues execution as if the call succeeded — leading to inconsistent state, lost funds, and exploitable logic. USDT's non-standard transfer() that returns nothing instead of a boolean has broken hundreds of contracts.
The Problem: Silent Failures
// VULNERABLE: Low-level call doesn't revert on failure
(bool success, ) = recipient.call{value: amount}("");
// 'success' is false but we never check it!
balance -= amount; // State updated even though transfer failed
// VULNERABLE: Some tokens don't return a boolean
IERC20(token).transfer(to, amount); // USDT returns nothing → silent fail
The ERC-20 standard says transfer() should return bool, but many popular tokens (USDT, BNB, OMG) either return nothing or return false instead of reverting. If your code expects a boolean return, these tokens break silently.
Common Unchecked Return Patterns
1. Unchecked call()
Low-level .call() returns a boolean but doesn't revert on failure. You must check it.
2. Non-Standard ERC-20 Tokens
USDT, USDC (partially), BNB, and ~300 other tokens have non-standard return behavior.
3. Unchecked send() and transfer()
.send() returns false on failure rather than reverting. .transfer() does revert but is gas-limited.
The SafeERC20 Solution
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract SecureVault {
using SafeERC20 for IERC20;
function deposit(IERC20 token, uint256 amount) external {
// SafeERC20 handles non-standard tokens and checks return values
token.safeTransferFrom(msg.sender, address(this), amount);
}
function withdraw(IERC20 token, uint256 amount) external {
token.safeTransfer(msg.sender, amount); // Reverts if transfer fails
}
}
// For low-level calls:
(bool success, ) = recipient.call{value: amount}("");
require(success, "Transfer failed"); // Always check!
- ✅ Always use SafeERC20 for token interactions
- ✅ Always check return values from
.call() - ✅ Use
require(success)after low-level calls - ✅ Test with USDT, BNB, and other non-standard tokens
- ✅ Avoid
.send()— use.call()with require
How Vultbase Detects Unchecked Returns
- Slither — Detects unchecked low-level calls, unused return values, and non-SafeERC20 token interactions
- Pattern DB — 21 unchecked return patterns from real vulnerabilities
- Semgrep — Custom rules for USDT-compatibility issues
Silent failures lead to loud exploits. Audit your external call handling before a non-standard token breaks your protocol.