| 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 |
1120×
1120×
1120×
1120×
7×
7×
1×
1×
2×
1×
1×
14×
13×
13×
35×
35×
35×
168×
168×
120×
120×
120×
6×
6×
6×
39×
39×
39×
4×
4×
4×
3×
3×
3×
3×
3×
3×
3×
589×
589×
588×
588×
588×
588×
94×
588×
582×
575×
575×
575×
575×
573×
35×
35×
35×
35×
35×
35×
35×
31×
28×
26×
20×
20×
22×
22×
22×
22×
22×
22×
22×
22×
22×
21×
20×
13×
10×
10×
10×
10×
10×
10×
10×
9×
9×
9×
1×
1×
9×
9×
9×
9×
6×
6×
6×
6×
2×
2×
2×
13×
13×
26×
2×
2×
42×
42×
42×
42×
39×
39×
39×
39×
36×
6×
1×
1×
31×
28×
25×
23×
23×
| // SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;
import "../BaseLogic.sol";
/// @notice Tokenised representation of assets
contract EToken is BaseLogic {
constructor(bytes32 moduleGitCommit_) BaseLogic(MODULEID__ETOKEN, moduleGitCommit_) {}
function CALLER() private view returns (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) {
(msgSender, proxyAddr) = unpackTrailingParams();
assetStorage = eTokenLookup[proxyAddr];
underlying = assetStorage.underlying;
require(underlying != address(0), "e/unrecognized-etoken-caller");
}
// Events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
// External methods
/// @notice Pool name, ie "Euler Pool: DAI"
function name() external view returns (string memory) {
(address underlying,,,) = CALLER();
return string(abi.encodePacked("Euler Pool: ", IERC20(underlying).name()));
}
/// @notice Pool symbol, ie "eDAI"
function symbol() external view returns (string memory) {
(address underlying,,,) = CALLER();
return string(abi.encodePacked("e", IERC20(underlying).symbol()));
}
/// @notice Decimals, always normalised to 18.
function decimals() external pure returns (uint8) {
return 18;
}
/// @notice Address of underlying asset
function underlyingAsset() external view returns (address) {
(address underlying,,,) = CALLER();
return underlying;
}
/// @notice Sum of all balances, in internal book-keeping units (non-increasing)
function totalSupply() external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return assetCache.totalBalances;
}
/// @notice Sum of all balances, in underlying units (increases as interest is earned)
function totalSupplyUnderlying() external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return balanceToUnderlyingAmount(assetCache, assetCache.totalBalances) / assetCache.underlyingDecimalsScaler;
}
/// @notice Balance of a particular account, in internal book-keeping units (non-increasing)
function balanceOf(address account) external view returns (uint) {
(, AssetStorage storage assetStorage,,) = CALLER();
return assetStorage.users[account].balance;
}
/// @notice Balance of a particular account, in underlying units (increases as interest is earned)
function balanceOfUnderlying(address account) external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return balanceToUnderlyingAmount(assetCache, assetStorage.users[account].balance) / assetCache.underlyingDecimalsScaler;
}
/// @notice Balance of the reserves, in internal book-keeping units (non-increasing)
function reserveBalance() external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return assetCache.reserveBalance;
}
/// @notice Balance of the reserves, in underlying units (increases as interest is earned)
function reserveBalanceUnderlying() external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return balanceToUnderlyingAmount(assetCache, assetCache.reserveBalance) / assetCache.underlyingDecimalsScaler;
}
/// @notice Convert an eToken balance to an underlying amount, taking into account current exchange rate
/// @param balance eToken balance, in internal book-keeping units (18 decimals)
/// @return Amount in underlying units, (same decimals as underlying token)
function convertBalanceToUnderlying(uint balance) external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return balanceToUnderlyingAmount(assetCache, balance) / assetCache.underlyingDecimalsScaler;
}
/// @notice Convert an underlying amount to an eToken balance, taking into account current exchange rate
/// @param underlyingAmount Amount in underlying units (same decimals as underlying token)
/// @return eToken balance, in internal book-keeping units (18 decimals)
function convertUnderlyingToBalance(uint underlyingAmount) external view returns (uint) {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
return underlyingAmountToBalance(assetCache, decodeExternalAmount(assetCache, underlyingAmount));
}
/// @notice Updates interest accumulator and totalBorrows, credits reserves, re-targets interest rate, and logs asset status
function touch() external nonReentrant {
(address underlying, AssetStorage storage assetStorage,,) = CALLER();
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
updateInterestRate(assetStorage, assetCache);
logAssetStatus(assetCache);
}
/// @notice Transfer underlying tokens from sender to the Euler pool, and increase account's eTokens
/// @param subAccountId 0 for primary, 1-255 for a sub-account
/// @param amount In underlying units (use max uint256 for full underlying token balance)
function deposit(uint subAccountId, uint amount) external nonReentrant {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
address account = getSubAccount(msgSender, subAccountId);
updateAverageLiquidity(account);
emit RequestDeposit(account, amount);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
if (amount == type(uint).max) {
amount = callBalanceOf(assetCache, msgSender);
}
amount = decodeExternalAmount(assetCache, amount);
uint amountTransferred = pullTokens(assetCache, msgSender, amount);
uint amountInternal;
// pullTokens() updates poolSize in the cache, but we need the poolSize before the deposit to determine
// the internal amount so temporarily reduce it by the amountTransferred (which is size checked within
// pullTokens()). We can't compute this value before the pull because we don't know how much we'll
// actually receive (the token might be deflationary).
unchecked {
assetCache.poolSize -= amountTransferred;
amountInternal = underlyingAmountToBalance(assetCache, amountTransferred);
assetCache.poolSize += amountTransferred;
}
increaseBalance(assetStorage, assetCache, proxyAddr, account, amountInternal);
if (assetStorage.users[account].owed != 0) checkLiquidity(account);
logAssetStatus(assetCache);
}
/// @notice Transfer underlying tokens from Euler pool to sender, and decrease account's eTokens
/// @param subAccountId 0 for primary, 1-255 for a sub-account
/// @param amount In underlying units (use max uint256 for full pool balance)
function withdraw(uint subAccountId, uint amount) external nonReentrant {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
address account = getSubAccount(msgSender, subAccountId);
updateAverageLiquidity(account);
emit RequestWithdraw(account, amount);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
uint amountInternal;
(amount, amountInternal) = withdrawAmounts(assetStorage, assetCache, account, amount);
require(assetCache.poolSize >= amount, "e/insufficient-pool-size");
pushTokens(assetCache, msgSender, amount);
decreaseBalance(assetStorage, assetCache, proxyAddr, account, amountInternal);
checkLiquidity(account);
logAssetStatus(assetCache);
}
/// @notice Mint eTokens and a corresponding amount of dTokens ("self-borrow")
/// @param subAccountId 0 for primary, 1-255 for a sub-account
/// @param amount In underlying units
function mint(uint subAccountId, uint amount) external nonReentrant {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
address account = getSubAccount(msgSender, subAccountId);
updateAverageLiquidity(account);
emit RequestMint(account, amount);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
amount = decodeExternalAmount(assetCache, amount);
uint amountInternal = underlyingAmountToBalanceRoundUp(assetCache, amount);
amount = balanceToUnderlyingAmount(assetCache, amountInternal);
// Mint ETokens
increaseBalance(assetStorage, assetCache, proxyAddr, account, amountInternal);
// Mint DTokens
increaseBorrow(assetStorage, assetCache, assetStorage.dTokenAddress, account, amount);
checkLiquidity(account);
logAssetStatus(assetCache);
}
/// @notice Pay off dToken liability with eTokens ("self-repay")
/// @param subAccountId 0 for primary, 1-255 for a sub-account
/// @param amount In underlying units (use max uint256 to repay the debt in full or up to the available underlying balance)
function burn(uint subAccountId, uint amount) external nonReentrant {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
address account = getSubAccount(msgSender, subAccountId);
updateAverageLiquidity(account);
emit RequestBurn(account, amount);
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
uint owed = getCurrentOwed(assetStorage, assetCache, account);
if (owed == 0) return;
uint amountInternal;
(amount, amountInternal) = withdrawAmounts(assetStorage, assetCache, account, amount);
if (amount > owed) {
amount = owed;
amountInternal = underlyingAmountToBalanceRoundUp(assetCache, amount);
}
// Burn ETokens
decreaseBalance(assetStorage, assetCache, proxyAddr, account, amountInternal);
// Burn DTokens
decreaseBorrow(assetStorage, assetCache, assetStorage.dTokenAddress, account, amount);
checkLiquidity(account);
logAssetStatus(assetCache);
}
/// @notice Allow spender to access an amount of your eTokens in sub-account 0
/// @param spender Trusted address
/// @param amount Use max uint256 for "infinite" allowance
function approve(address spender, uint amount) external reentrantOK returns (bool) {
return approveSubAccount(0, spender, amount);
}
/// @notice Allow spender to access an amount of your eTokens in a particular sub-account
/// @param subAccountId 0 for primary, 1-255 for a sub-account
/// @param spender Trusted address
/// @param amount Use max uint256 for "infinite" allowance
function approveSubAccount(uint subAccountId, address spender, uint amount) public nonReentrant returns (bool) {
(, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
address account = getSubAccount(msgSender, subAccountId);
require(!isSubAccountOf(spender, account), "e/self-approval");
assetStorage.eTokenAllowance[account][spender] = amount;
emitViaProxy_Approval(proxyAddr, account, spender, amount);
return true;
}
/// @notice Retrieve the current allowance
/// @param holder Xor with the desired sub-account ID (if applicable)
/// @param spender Trusted address
function allowance(address holder, address spender) external view returns (uint) {
(, AssetStorage storage assetStorage,,) = CALLER();
return assetStorage.eTokenAllowance[holder][spender];
}
/// @notice Transfer eTokens to another address (from sub-account 0)
/// @param to Xor with the desired sub-account ID (if applicable)
/// @param amount In internal book-keeping units (as returned from balanceOf).
function transfer(address to, uint amount) external reentrantOK returns (bool) {
return transferFrom(address(0), to, amount);
}
/// @notice Transfer the full eToken balance of an address to another
/// @param from This address must've approved the to address, or be a sub-account of msg.sender
/// @param to Xor with the desired sub-account ID (if applicable)
function transferFromMax(address from, address to) external reentrantOK returns (bool) {
(, AssetStorage storage assetStorage,,) = CALLER();
return transferFrom(from, to, assetStorage.users[from].balance);
}
/// @notice Transfer eTokens from one address to another
/// @param from This address must've approved the to address, or be a sub-account of msg.sender
/// @param to Xor with the desired sub-account ID (if applicable)
/// @param amount In internal book-keeping units (as returned from balanceOf).
function transferFrom(address from, address to, uint amount) public nonReentrant returns (bool) {
(address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
if (from == address(0)) from = msgSender;
require(from != to, "e/self-transfer");
updateAverageLiquidity(from);
updateAverageLiquidity(to);
emit RequestTransferEToken(from, to, amount);
if (amount == 0) return true;
if (!isSubAccountOf(msgSender, from) && assetStorage.eTokenAllowance[from][msgSender] != type(uint).max) {
require(assetStorage.eTokenAllowance[from][msgSender] >= amount, "e/insufficient-allowance");
unchecked { assetStorage.eTokenAllowance[from][msgSender] -= amount; }
emitViaProxy_Approval(proxyAddr, from, msgSender, assetStorage.eTokenAllowance[from][msgSender]);
}
transferBalance(assetStorage, assetCache, proxyAddr, from, to, amount);
checkLiquidity(from);
if (assetStorage.users[to].owed != 0) checkLiquidity(to);
logAssetStatus(assetCache);
return true;
}
}
|