Skip to main content
Sigvex

Wrapped Native Token Remediation

How to handle ETH and WETH consistently so deposits, accounting, and transfers never diverge.

Wrapped Native Token Remediation

Overview

Related Detector: Wrapped Native Token

Contracts that accept native ETH but account in WETH (or vice versa) drift out of sync: ETH arrives via msg.value while balances are tracked in WETH, transfers pay out the wrong asset, or a value is counted twice when both paths exist. The remediation is to normalize at the boundary — pick one internal representation and convert consistently on the way in and out.

Before (Vulnerable)

function deposit() external payable {
    // Accepts ETH but credits a WETH balance that was never actually wrapped.
    wethBalance[msg.sender] += msg.value;
}

After (Fixed — wrap on entry, unwrap on exit, one representation)

IWETH public immutable weth;

function deposit() external payable {
    weth.deposit{value: msg.value}();          // ETH -> WETH at the boundary
    wethBalance[msg.sender] += msg.value;       // now backed by real WETH
}

function withdraw(uint256 amount) external {
    require(wethBalance[msg.sender] >= amount, "Insufficient");
    wethBalance[msg.sender] -= amount;
    weth.withdraw(amount);                       // WETH -> ETH
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "Transfer failed");
}

Choose either native ETH or WETH as the single internal unit and convert at every edge, so no code path can double-count or pay the wrong asset.

Common Mistakes

  • Supporting both an ETH path and a WETH path for the same operation and counting the deposit in both.
  • Crediting a WETH balance on an ETH deposit without actually calling weth.deposit, so the backing does not exist.
  • Sending native ETH to a contract that only implements a WETH receive path (or vice versa), stranding funds.

References