Skip to main content
Sigvex

Wrapped Native Token Confusion

Detects contracts that conflate WETH and native ETH handling, causing stuck funds or incorrect transfer logic.

Wrapped Native Token Confusion

Overview

The wrapped native token detector identifies contracts that inconsistently handle WETH (Wrapped Ether) and native ETH. Common bugs include accepting ETH deposits but accounting for WETH balances, failing to unwrap WETH before native transfers, or double-counting when both WETH and ETH paths exist for the same operation.

Why This Is an Issue

ETH and WETH are economically equivalent but mechanically different. ETH transfers use msg.value and call{value: x}, while WETH uses ERC-20 transfer/transferFrom. Contracts that handle both must:

  • Track which asset was deposited and withdraw the same type
  • Avoid double-counting when both paths credit the same internal balance
  • Handle the WETH deposit/withdrawal gas correctly

Millions in ETH have been permanently stuck in contracts due to WETH/ETH confusion, particularly in DEX routers, bridges, and yield aggregators.

How to Resolve

// Before: Accepts both ETH and WETH but only withdraws as WETH
function deposit() external payable {
    balances[msg.sender] += msg.value; // ETH credited
    // But withdraw() only does WETH.transfer() -- ETH is stuck
}

// After: Consistent handling with explicit unwrap
function deposit() external payable {
    WETH.deposit{value: msg.value}(); // Convert ETH to WETH immediately
    balances[msg.sender] += msg.value;
}

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;
    WETH.withdraw(amount); // Convert back to ETH
    payable(msg.sender).call{value: amount}("");
}

Examples

Vulnerable Code

contract VulnerableRouter {
    function swapExactETH(address tokenOut, uint256 minOut) external payable {
        // Wraps ETH to WETH for the swap
        WETH.deposit{value: msg.value}();
        // Performs swap via WETH
        uint256 amountOut = _swap(address(WETH), tokenOut, msg.value);
        require(amountOut >= minOut);
        // Refund logic sends remaining WETH balance as ETH
        uint256 remaining = WETH.balanceOf(address(this));
        if (remaining > 0) {
            // BUG: forgets to unwrap -- sends WETH tokens, not ETH
            WETH.transfer(msg.sender, remaining);
            // User expected ETH refund but received WETH
        }
    }
}

Fixed Code

contract SafeRouter {
    function swapExactETH(address tokenOut, uint256 minOut) external payable {
        WETH.deposit{value: msg.value}();
        uint256 amountOut = _swap(address(WETH), tokenOut, msg.value);
        require(amountOut >= minOut);
        uint256 remaining = WETH.balanceOf(address(this));
        if (remaining > 0) {
            WETH.withdraw(remaining); // Unwrap first
            (bool success, ) = msg.sender.call{value: remaining}("");
            require(success, "ETH refund failed");
        }
    }
}

Sample Sigvex Output

{
  "detector_id": "wrapped-native-token",
  "severity": "high",
  "confidence": 0.76,
  "description": "Function swapExactETH() accepts native ETH (msg.value) and wraps to WETH, but refund path transfers WETH tokens instead of native ETH. Users expecting ETH refund receive ERC-20 WETH.",
  "location": { "function": "swapExactETH(address,uint256)", "offset": 184 }
}

Detection Methodology

  1. WETH pattern detection: Identifies WETH deposit/withdraw calls by selector and known WETH contract addresses.
  2. ETH flow tracking: Traces msg.value through the function and identifies where it is consumed (WETH.deposit, call{value}, etc.).
  3. Consistency check: Verifies that ETH deposits result in ETH withdrawals (or that WETH conversion is properly handled in both directions).
  4. Balance analysis: Detects potential trapped ETH when native ETH is received but only ERC-20 transfers are used for outputs.

Limitations

  • The detector relies on recognizing WETH contract addresses. Non-standard wrapped token contracts on newer L2s may not be identified.
  • Contracts that intentionally accept ETH and hold it in WETH form by design will be flagged.
  • Multi-hop swaps where WETH conversion happens in a called contract are not tracked.

References