high

Unchecked Return Value: How It Works, Real Exploits & Automated Detection

April 1, 2026
Chainsethereumarbitrumbaseoptimismpolygonbnb-chain
Detected byslithermythrilechidnahound-ai

Unchecked return value vulnerabilities occur when contracts ignore the return status of external calls, assuming success and proceeding to modify state. If the external call fails, the contract continues with incorrect assumptions, breaking invariants. Common in token transfers and low-level calls. Firepan's HOUND AI detects unchecked return value vulnerabilities automatically during development and post-deployment monitoring, covering continuous analysis across the full contract lifecycle.

What Is Unchecked Return Value?

Unchecked return value vulnerability arises when:

  1. Low-level call (.call, .staticcall, .delegatecall): Returns (bool, bytes); contract ignores bool
  2. Token transfer (.transfer, .transferFrom): Might fail silently; contract doesn't check
  3. Approve without return check: approve() might return false; contract assumes success
  4. External function calls: External functions return values; contract ignores them

The vulnerability is critical because:

  • Silent failures: Transaction succeeds, but external call failed
  • State corruption: Contract modifies state assuming call succeeded
  • Financial loss: Tokens appear transferred but actually weren't
  • Authorization bypass: Approval appears granted but wasn't

How Unchecked Return Value Works

Unchecked return value exploitation involves:

  1. Identify unchecked call: Find external call without return value check
  2. Trigger silent failure: Force external call to fail (e.g., token transfer from empty address)
  3. Exploit state change: Contract modifies state assuming call succeeded
  4. Extract value: Use corrupted state to steal funds or privileges
// VULNERABLE — example only
// Demonstrates: Unchecked Return Value
// Do NOT use in production

pragma solidity ^0.8.0;

contract VulnerableToken {
    mapping(address => uint256) public balances;

    // VULNERABLE: Doesn't check transfer return value
    function transferTokens(address token, address to, uint256 amount) public {
        // Low-level call, return value ignored
        token.call(abi.encodeWithSignature("transfer(address,uint256)", to, amount));

        // Contract assumes transfer succeeded
        // But if call failed (e.g., invalid token), execution continues
    }

    // VULNERABLE: transfer() return value ignored
    function withdrawToken(address token, uint256 amount) public {
        require(balances[msg.sender] >= amount);

        balances[msg.sender] -= amount;  // Update state BEFORE checking success

        // VULNERABLE: Doesn't check return value
        IERC20(token).transfer(msg.sender, amount);

        // If transfer fails, balance is still decremented!
    }

    // VULNERABLE: approve return value ignored
    function approveSpender(address token, address spender, uint256 amount) public {
        require(balances[msg.sender] >= amount);

        // VULNERABLE: Doesn't check return value
        IERC20(token).approve(spender, amount);

        // Assume approved; but if approve returned false, contract thinks spender is approved
    }

    // VULNERABLE: transferFrom return value ignored
    function receiveTokens(address token, address from, uint256 amount) public {
        // Low-level call, return value ignored
        (bool success, ) = token.call(
            abi.encodeWithSignature("transferFrom(address,address,uint256)", from, address(this), amount)
        );

        // Ignore return value!
        balances[address(this)] += amount;

        // If transferFrom failed, balance increased without funds received
    }
}

contract UncheckedReturnAttacker {
    address public vulnerable;
    address public fakeToken;

    function exploitTransfer() public {
        VulnerableToken target = VulnerableToken(vulnerable);

        // Create fake token that returns false on transfer
        FakeToken fake = new FakeToken();

        // Call target.transferTokens with fake token
        // transferTokens calls fake.transfer(), which fails
        // But target doesn't check return value, so execution continues as if successful

        target.transferTokens(address(fake), address(this), 1000 ether);
    }

    function exploitWithdraw() public {
        // Send 100 ether to target's balance
        // Call withdraw()
        // withdraw() decrements balance but transfer fails
        // Balance is corrupted; attacker can re-withdraw

        target.withdrawToken(fakeToken, 100 ether);
        target.withdrawToken(fakeToken, 100 ether);  // Withdraw again! Balance already at 0
    }
}

contract FakeToken {
    function transfer(address to, uint256 amount) public pure returns (bool) {
        // Always return false (failure)
        return false;
    }

    function transferFrom(address from, address to, uint256 amount) public pure returns (bool) {
        return false;
    }

    function approve(address spender, uint256 amount) public pure returns (bool) {
        return false;
    }
}

Real-World Unchecked Return Value Exploits

| Protocol | Date | Loss | Root Cause | |----------|------|------|-----------| | Audius | 2022 | $18M | Unchecked token transfer return in delegation | | Compound | 2020 | $19M | Unchecked cToken.transfer return value | | SIlvergate | 2021 | $1M+ | Unchecked approve return in wrapped tokens |

How to Detect Unchecked Return Value

Manual detection requires return value verification:

  • Low-level calls: Verify all .call(), .staticcall(), .delegatecall() check return value
  • Token transfers: Confirm transfer() and transferFrom() return values are checked
  • Approve operations: Verify approve() return values are validated
  • External calls: Check all external function calls verify return values
  • ERC20 compliance: Verify safeTransfer patterns used for non-standard ERC20 tokens

Red flags:

  • Low-level call without (bool, bytes) assignment or check
  • transfer() or transferFrom() used without return check
  • approve() without return verification
  • State modified before return value checked
  • External function return values ignored
  • Missing SafeERC20 wrapper usage

How Firepan Detects Unchecked Return Value Automatically

Firepan's HOUND AI performs return value analysis:

  1. External call enumeration: Identifies all external calls
  2. Return value mapping: Tracks which calls return values
  3. Return value usage: Verifies returned values are checked
  4. State modification ordering: Confirms state updates occur after return check
  5. ERC20 pattern detection: Identifies token transfer patterns
  6. Silent failure scenario modeling: Constructs scenarios where calls fail silently

Firepan's HOUND AI engine identifies unchecked return vulnerabilities across all monitored contracts.

Prevention Best Practices

1. Always Check Low-Level Call Return Values

Never ignore (bool, bytes) from .call():

// VULNERABLE
function sendEther(address to, uint256 amount) public {
    to.call{value: amount}("");  // Return value ignored!
}

// SECURE
function sendEther(address to, uint256 amount) public {
    (bool success, ) = to.call{value: amount}("");
    require(success, "ETH transfer failed");
}

2. Use SafeERC20 Wrapper

OpenZeppelin's SafeERC20 checks return values:

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract SecureToken {
    using SafeERC20 for IERC20;

    function withdraw(address token, uint256 amount) public {
        require(balances[msg.sender] >= amount);

        balances[msg.sender] -= amount;

        // SafeERC20.transfer checks return value and reverts on failure
        IERC20(token).safeTransfer(msg.sender, amount);
    }
}

3. Check Token Transfer Return Values

Always verify transfer() and transferFrom() return true:

// SECURE
function receiveTokens(address token, address from, uint256 amount) public {
    require(
        IERC20(token).transferFrom(from, address(this), amount),
        "Transfer failed"
    );

    balances[address(this)] += amount;
}

4. Verify Approve Return Values

Check that approve() returns true:

// SECURE
function approveSpender(address token, address spender, uint256 amount) public {
    require(
        IERC20(token).approve(spender, amount),
        "Approval failed"
    );
}

5. Update State After Verified Calls

Always modify state AFTER confirming external call succeeded:

// SECURE: Check BEFORE state update
function withdraw(address token, uint256 amount) public {
    require(balances[msg.sender] >= amount);

    // Verify transfer succeeds
    require(
        IERC20(token).transfer(msg.sender, amount),
        "Transfer failed"
    );

    // Update state AFTER verified success
    balances[msg.sender] -= amount;
}

Frequently Asked Questions

Q: What is unchecked return value in smart contracts?

A: Unchecked return value occurs when contracts ignore the return status of external calls, assuming success and modifying state. If the call fails, state is corrupted, causing financial loss or authorization bypass.


Q: Which protocols have been exploited via unchecked return?

A: Audius ($18M, 2022) suffered unchecked token transfer in delegation. Compound ($19M, 2020) had unchecked cToken transfer return. Silvergate ($1M+, 2021) had unchecked approve return in wrapped tokens.


Q: How does Firepan detect unchecked return value?

A: Firepan enumerates external calls, traces return value usage, verifies returned values are checked, confirms state updates occur after return checks, detects ERC20 patterns, and models silent failure scenarios.


Q: Can unchecked return value be exploited after deployment?

A: Yes, unchecked return vulnerabilities are immediately exploitable post-deployment. Attackers use fake tokens or non-standard ERC20 implementations to trigger silent failures.


Q: How do I prevent unchecked return value?

A: Always check (bool, bytes) from low-level calls. Use SafeERC20 wrapper for token operations. Verify transfer() and transferFrom() return values. Verify approve() return values. Update state after calls, not before.

Conclusion

Unchecked return values have cost DeFi over $50M, particularly in token operations. Audius's $18M loss and Compound's $19M loss demonstrated the impact. Using SafeERC20 and verifying all return values eliminates this vulnerability class. Firepan's HOUND AI detects missing return value checks across all monitored contracts.

Start securing your smart contracts at https://app.firepan.com/

Firepan

Scan Your Contracts Now

12,453 contracts secured. 2,851 vulnerabilities blocked. 236 exploits prevented. Run a free surface scan — results in minutes, no credit card required.

Run Free Scan →