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.
Recommended Fix
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
receivepath (or vice versa), stranding funds.