AnyswapV4Router WETH9 permit misuse drains WETH to ETH
Exploit Transactions
0xbef94b2a98d0d51ee9c2f65904b45b26f4a7e621a197a325f7fdf06626ed79b40xe50ed602bd916fc304d53c4fed236698b71691a95774ff0aeeb74b699c6227f70x35ea606d5097d387b938dac6fd1be173d9b6610290a850f868c38c7dc7419667Victim Addresses
0x3e1f13608111de38ec4bd97588d8636718f49516Ethereum0x3Ee505bA316879d246a8fD2b3d7eE63b51B44FABEthereum0xd9f3f702db5d4fe3fcdb70e396c1e4f4cde24315EthereumLoss Breakdown
Similar Incidents
WETH Drain via Unprotected 0xfa461e33 Callback on 0x03f9-62c0
39%WETH9 Balance Drain via Router 0x5697-1751 Using Victim
38%GPv2Settlement allowance leak lets router drain WETH and USDC
37%OMPxContract bonding-curve loop exploit drains ETH reserves
36%USDC Allowances Drained via ACOWriter Misuse
36%SBR reserve desynchronization exploit drains WETH from UniswapV2 pair
35%Root Cause Analysis
AnyswapV4Router WETH9 permit misuse drains WETH to ETH
1. Incident Overview TL;DR
On Ethereum mainnet, an unprivileged adversary abused the anySwapOutUnderlyingWithPermit function of AnyswapV4Router at 0x6b7a87899490ece95443e979ca9485cbe7e71522 together with canonical WETH9 at 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2. By calling a helper contract 0xb5c827fdbbee6f6e9df3a5cb499aedf5927de1b8 entrypoint copyyouattack(address victim, uint256 amount, uint256 bribePercent), the adversary’s EOA 0xfa2731d0bede684993ab1109db7ecf5bf33e8051 triggered router calls that treated a forged permit call on WETH9 as a no-op, then used victims’ pre-existing WETH9 allowances to transfer their balances into the helper and withdraw them to ETH. Across three transactions (txs 0xbef94b2a…, 0xe50ed602…, 0x35ea606d…) in blocks 14037030, 14037237, and 14037497, the attacker drained a total of 312.166644758370382903 WETH9 from three victims and realized approximately 277.787166105615083004 ETH net profit after paying miner bribes and gas.
2. Key Background
Anyswap/Multichain’s V4 router exposes cross-chain bridging functions that accept an Anyswap token and interact with its underlying ERC-20 token. For WETH bridging, the relevant contracts are:
- AnyswapV4Router at
0x6b7a87899490ece95443e979ca9485cbe7e71522(verified source available in the artifacts). - WETH9 at
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, the canonical wrapped Ether implementation. - A helper Anyswap-style token contract at
0xb5c827fdbbee6f6e9df3a5cb499aedf5927de1b8, which the adversary uses as anAnyswapV1ERC20-compatible token whoseunderlying()is WETH9.
The router’s anySwapOutUnderlyingWithPermit function is intended to let a user bridge an underlying token using an ERC‑2612-style permit signature so that they do not need to submit a prior approve transaction. Its relevant definition (from the verified router source) is:
function anySwapOutUnderlyingWithPermit(
address from,
address token,
address to,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint toChainID
) external {
address _underlying = AnyswapV1ERC20(token).underlying();
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s);
TransferHelper.safeTransferFrom(_underlying, from, token, amount);
AnyswapV1ERC20(token).depositVault(amount, from);
_anySwapOut(from, token, to, amount, toChainID);
}
This code assumes that _underlying implements permit and that calling it will safely establish an allowance from from to the router. However, WETH9 does not implement permit at all; instead, it only exposes deposit, withdraw, transfer, and transferFrom, and uses a payable fallback that calls deposit() on receipt of ETH:
contract WETH9 {
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
}
function transferFrom(address src, address dst, uint wad) public returns (bool) {
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
return true;
}
}
A CALL to WETH9 with the router’s permit selector 0xd505accf and zero ETH therefore succeeds via the fallback, does not modify allowances, and does not revert.
The helper contract at 0xb5c8… is decompiled in the artifacts and includes the adversary-facing entrypoint:
/// @custom:selector 0x455a3191
/// @custom:signature copyyouattack(address victim, uint256 amount, uint256 bribePercent) public
function copyyouattack(address victim, uint256 amount, uint256 bribePercent) public {
// read victim WETH9 balance
// ...
// call router anySwapOutUnderlyingWithPermit
// from = victim
// token = address(this) (0xb5c8...)
// to = address(this)
// amount = amount
// deadline, v, r, s = fixed constants
// router then calls WETH9.permit (no-op) and WETH9.transferFrom(victim, 0xb5c8..., amount)
// withdraw WETH9 to ETH and pay bribePercent% to block.coinbase
// send remaining ETH balance to the owner-derived address linked to 0xfa2731...
}
Traces for the three exploit transactions show this exact sequence: 0xfa2731… calls copyyouattack on 0xb5c8…, which calls anySwapOutUnderlyingWithPermit on the router with from set to the victim address and token set to 0xb5c8…, causing WETH9 balances to be pulled from the victim into the helper and immediately withdrawn to ETH.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability arises from AnyswapV4Router’s misuse of anySwapOutUnderlyingWithPermit for tokens that do not implement ERC‑2612 permit. The function accepts an arbitrary from address and unconditionally calls IERC20(_underlying).permit(from, address(this), amount, ...) on the underlying token, then performs safeTransferFrom(_underlying, from, token, amount) without checking that permit succeeded or that msg.sender is equal to from.
For WETH9, which has no permit function and instead routes the call to its payable fallback, the router’s permit call becomes a no-op that always “succeeds”. As long as the victim has previously approved the router as a spender in the standard WETH9 allowance mapping, the subsequent safeTransferFrom will succeed when called by the router even though the transaction was initiated entirely by an unprivileged third-party helper.
The concrete invariant that is broken is:
For any EOA or contract
vholding WETH9 on Ethereum, an unprivileged third party that does not controlv’s private key or a valid signed permit must not be able to reduceWETH9.balanceOf(v)via AnyswapV4Router by more thanvhas explicitly authorized through direct transactions or valid permit signatures.
The breakpoint occurs inside anySwapOutUnderlyingWithPermit when the router:
- Resolves
_underlying = AnyswapV1ERC20(token).underlying()to WETH9 for token0xb5c8…. - Calls
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s)on WETH9, which silently accepts the call via its fallback and does not change allowances. - Immediately executes
TransferHelper.safeTransferFrom(_underlying, from, token, amount)withfromset to a victim EOA that has pre-approved the router.
This logic allows any unprivileged actor to drain WETH9 from any address that has granted the router sufficient allowance, simply by setting from = victim in the call and using a helper token whose underlying() is WETH9.
4. Detailed Root Cause Analysis
Pre-state and success conditions
At block 14037030, before tx 0xbef94b2a98d0d51ee9c2f65904b45b26f4a7e621a197a325f7fdf06626ed79b4, the following conditions hold:
- WETH9 at
0xc02a…holds balances for three victims:0x3e1f13608111de38ec4bd97588d8636718f495160x3Ee505bA316879d246a8fD2b3d7eE63b51B44FAB0xd9f3f702db5d4fe3fcdb70e396c1e4f4cde24315
- Each victim has previously executed a standard
approveon WETH9 granting AnyswapV4Router (0x6b7a…) a non-zero allowance large enough for the observedtransferFromamounts; this follows from the successfultransferFromcalls in the traces. - AnyswapV4Router and helper
0xb5c8…are deployed with the code shown above, and helperunderlying()resolves to WETH9.
The adversary’s success predicate is purely monetary: the net ETH value of the adversary-related cluster {0xfa2731…, 0xb5c8…} increases after accounting for gas costs and explicit bribes to block.coinbase.
Exploit transaction 1: 0xbef94b2a… (block 14037030)
In the first exploit transaction, 0xfa2731… calls helper 0xb5c8… with selector 0x455a3191 and parameters targeting victim 0x3e1f13608111de38ec4bd97588d8636718f49516 for an amount of exactly 0.08 WETH. The callTracer trace includes the following key calls:
{
"from": "0x6b7a87899490ece95443e979ca9485cbe7e71522",
"to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"type": "CALL",
"input": "0xd505accf...", // permit-style selector
"value": "0x0"
},
{
"from": "0x6b7a87899490ece95443e979ca9485cbe7e71522",
"to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"type": "CALL",
"input": "0x23b872dd...3e1f1360... -> 0xb5c8...", // WETH9.transferFrom(victim, helper, 0.08 WETH)
"value": "0x0"
},
{
"from": "0xb5c8...",
"to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"type": "CALL",
"input": "0x2e1a7d4d...", // WETH9.withdraw(0.08 WETH)
"value": "0x0"
}
After the withdraw, helper 0xb5c8… forwards 0.0088 ETH to block.coinbase and 0.0712 ETH to 0xfa2731…, realizing a small profit and confirming that the route from victim WETH9 to adversary ETH operates as described.
Exploit transaction 2 (seed): 0xe50ed602… (block 14037237)
The seed transaction is the main exploit:
- Sender:
0xfa2731d0bede684993ab1109db7ecf5bf33e8051. - To: helper
0xb5c8…. - Calldata:
copyyouattack(victim = 0x3Ee505bA316879d246a8fD2b3d7eE63b51B44FAB, amount = 308636644758370382903, bribePercent = 11).
The decompiled copyyouattack function and the callTracer trace show:
- Helper calls
WETH9.balanceOf(0x3Ee5…)to read the victim’s WETH balance. - Helper constructs a call to AnyswapV4Router
anySwapOutUnderlyingWithPermitwith:from = 0x3Ee5…(victim EOA, not equal tomsg.senderof the outer transaction).token = 0xb5c8….to = 0xb5c8….amount = 308636644758370382903.deadline,v,r,sas fixed constants (not derived from any victim signature).
- Inside the router, the trace records:
STATICCALLto0xb5c8…selector0x6f307dc3to fetchunderlying(), returning WETH9 at0xc02a….CALLto WETH9 with selector0xd505accfand zero value (the supposedpermit), which does not revert.CALLto WETH9 with selector0x23b872ddtransferring exactly308636644758370382903WETH9 from0x3Ee5…to0xb5c8….
- Helper then calls
WETH9.withdraw(308636644758370382903)and receives the same amount in ETH. - Finally, helper transfers:
- 33.950030923420742119 ETH to
block.coinbase0xea674fdde714fd979de3edf0f56aa9716b898ec8as a bribe. - 274.686613834949640784 ETH to
0xfa2731….
- 33.950030923420742119 ETH to
The prestate tracer diff for this transaction shows WETH9’s storage for 0x3Ee5… decreasing by exactly 308636644758370382903 wei and the WETH9 contract balance decreasing by the same amount when withdraw is called. It also shows the ETH balances:
0xfa2731…: +274.673279555369329864 ETH net (after gas).0xea674f…: +33.950030923420742119 ETH.- WETH9: –308.636644758370382903 ETH (as underlying).
No WETH9 allowance mapping entries for
0x3Ee5…are modified in this transaction, confirming that the exploit uses pre-existing approvals and does not rely on a new permit.
Exploit transaction 3: 0x35ea606d… (block 14037497)
The third transaction repeats the same pattern for victim 0xd9f3f702db5d4fe3fcdb70e396c1e4f4cde24315 with an amount of 3.45 WETH9. The trace again shows the router calling anySwapOutUnderlyingWithPermit, making a non-reverting permit-style CALL into WETH9, then calling transferFrom and withdraw, after which helper 0xb5c8… pays a bribe to block.coinbase and forwards the remaining ETH to 0xfa2731….
Victim remediation
After the seed exploit, victim 0x3Ee5… clears its WETH9 allowance to the router. The txlist artifact for this address contains:
{
"hash": "0x7010eec66a20167403e84b1c8ab50803f92bf4fce450ce2d81248805b11eb025",
"from": "0x3ee505ba316879d246a8fd2b3d7ee63b51b44fab",
"to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"input": "0x095ea7b3...6b7a87899490ece95443e979ca9485cbe7e71522...0000000000000000000000000000000000000000000000000000000000000000",
"functionName": "approve(address _spender, uint256 _value)",
"blockNumber": "14037295"
}
This is WETH9.approve(0x6b7a8789…, 0), which revokes the router’s allowance and confirms that the victim is an EOA responding to the observed theft.
5. Adversary Flow Analysis
The adversary-related cluster consists minimally of:
- EOA
0xfa2731d0bede684993ab1109db7ecf5bf33e8051(transaction sender and ETH profit recipient). - Helper contract
0xb5c827fdbbee6f6e9df3a5cb499aedf5927de1b8(attacker-controlled orchestrator that interacts with the router and WETH9).
The end-to-end flow is:
- Preparation (off-chain / prior activity): Victims interact with Anyswap/Multichain, acquire WETH9, and execute standard
WETH9.approve(0x6b7a…, allowance)transactions so that the router can move WETH9 on their behalf. These approvals exist before block 14037030 and are not created by the adversary. - Exploit transactions: For each victim address and amount,
0xfa2731…submits a 0 ETH EIP‑1559 transaction callingcopyyouattack(victim, amount, bribePercent)on helper0xb5c8…:- The helper reads the victim’s WETH9 balance and constructs a call into AnyswapV4Router
anySwapOutUnderlyingWithPermitwithfrom = victim,token = 0xb5c8…,to = 0xb5c8…,amount = victimAmount, and a fixed bogus permit tuple. - The router resolves the underlying as WETH9 and calls
IERC20(WETH9).permit(...). WETH9 treats this as a fallbackdepositwith zero value, leaving allowances unchanged and not reverting. - The router unconditionally calls
WETH9.transferFrom(victim, 0xb5c8…, amount), which succeeds because the victim previously approved the router. - The router calls the helper’s stub
depositVault, and its internal accounting_anySwapOutburns helper tokens, but this accounting does not affect the theft. - Helper
0xb5c8…immediately callsWETH9.withdraw(amount), receiving ETH equal to the drained WETH9. - Helper pays
bribePercentof the ETH (e.g., 11%) toblock.coinbaseto incentivize inclusion, and forwards the remainder to0xfa2731….
- The helper reads the victim’s WETH9 balance and constructs a call into AnyswapV4Router
- Profit realization: For the three observed transactions, the net ETH gains are:
- Tx
0xbef94b2a…: ~0.0712 ETH after a small bribe. - Tx
0xe50ed602…: 274.673279555369329864 ETH net to0xfa2731…(per prestate state diff and gas usage). - Tx
0x35ea606d…: profit consistent with 3.45 WETH9 minus bribe and gas, forwarded to0xfa2731….
- Tx
Because the only requirements are (a) existing WETH9 allowances from victims to the router and (b) the ability to deploy or call a helper token whose underlying() is WETH9, the opportunity is an Anyone‑Can‑Take (ACT) strategy. Any unprivileged EOA observing that a given address has granted WETH9 allowance to 0x6b7a… can reproduce this flow with their own helper contract and EOA.
6. Impact & Losses
Across the three exploit transactions, the victims lose a total of 312.166644758370382903 WETH9, withdrawn to ETH via WETH9’s withdraw function. The per-transaction breakdown is:
- Tx
0xbef94b2a98d0d51ee9c2f65904b45b26f4a7e621a197a325f7fdf06626ed79b4: 0.08 WETH9 drained from0x3e1f13608111de38ec4bd97588d8636718f49516. - Tx
0xe50ed602bd916fc304d53c4fed236698b71691a95774ff0aeeb74b699c6227f7: 308.636644758370382903 WETH9 drained from0x3Ee505bA316879d246a8fD2b3d7eE63b51B44FAB. - Tx
0x35ea606d5097d387b938dac6fd1be173d9b6610290a850f868c38c7dc7419667: 3.45 WETH9 drained from0xd9f3f702db5d4fe3fcdb70e396c1e4f4cde24315.
The ETH flows derived from traces and balance diffs are:
- Total WETH9 withdrawn to ETH: 312.166644758370382903 ETH.
- Miner bribes (to
0xea674fdde714fd979de3edf0f56aa9716b898ec8): 34.338330923420742119 ETH across the three transactions. - Gas costs for
0xfa2731…: approximately 0.041147729334558 ETH in total. - Net ETH profit to the adversary EOA
0xfa2731…: 277.787166105615083004 ETH.
No WETH9 allowances are modified during the exploit transactions, and AnyswapV4Router’s internal accounting is unaffected; the only persistent impact is the irreversible loss of WETH9/ETH from the three victim addresses whose allowances were abused.
7. References
Key artifacts that substantiate this analysis are:
- Seed transaction metadata for
0xe50ed602bd916fc304d53c4fed236698b71691a95774ff0aeeb74b699c6227f7(includes gas usage and sender/recipient addresses). callTracertraces for the three exploit transactions0xbef94b2a…,0xe50ed602…,0x35ea606d…, showing the full call stack through helper0xb5c8…, AnyswapV4RouteranySwapOutUnderlyingWithPermit, WETH9transferFrom,withdraw, and ETH forwarding.state_diff_prestateTracerfor0xe50ed602…, confirming the exact WETH9 and ETH balance changes for victims, WETH9, and the adversary EOA.- Verified AnyswapV4Router source at
0x6b7a87899490ece95443e979ca9485cbe7e71522, especially the implementation ofanySwapOutUnderlyingWithPermit. - Canonical WETH9 source at
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, confirming the absence ofpermitand the behavior of its fallback,deposit,withdraw, andtransferFrom. - Decompiled helper contract source for
0xb5c827fdbbee6f6e9df3a5cb499aedf5927de1b8, includingcopyyouattack,withdrawethamount, and ETH forwarding behavior. - Txlists for the adversary EOA
0xfa2731d0bede684993ab1109db7ecf5bf33e8051and victim address0x3Ee505bA316879d246a8fD2b3d7eE63b51B44FAB, demonstrating the three exploit transactions and the post-incident allowance revocation.