-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathCNumaLst.sol
437 lines (392 loc) · 15.6 KB
/
CNumaLst.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
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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity 0.8.20;
import "./CNumaToken.sol";
import "@openzeppelin/contracts_5.0.2/token/ERC20/utils/SafeERC20.sol";
/**
* @title CNumaLst
* @notice CTokens used with numa vault
* @author
*/
contract CNumaLst is CNumaToken {
constructor(
address underlying_,
ComptrollerInterface comptroller_,
InterestRateModel interestRateModel_,
uint initialExchangeRateMantissa_,
string memory name_,
string memory symbol_,
uint8 decimals_,
uint fullUtilizationRate_,
address payable admin_,
address _vault
)
CNumaToken(
underlying_,
comptroller_,
interestRateModel_,
initialExchangeRateMantissa_,
name_,
symbol_,
decimals_,
fullUtilizationRate_,
admin_,
_vault
)
{}
/**
* @notice Returns the current per-block borrow interest rate for this cToken
* @return The borrow interest rate per block, scaled by 1e18
*/
function borrowRatePerBlock() external view override returns (uint) {
// NUMALENDING
// borrow rate is based on lending contract cash & vault available to borrow
uint maxBorrowableAmountFromVault;
if (address(vault) != address(0))
maxBorrowableAmountFromVault = vault.getMaxBorrow();
uint currentTimestamp = block.timestamp;
uint timestampPrior = accrualBlockTimestamp;
uint deltaTime = currentTimestamp - timestampPrior;
(uint ratePerBlock, ) = interestRateModel.getBorrowRate(
getCashPrior() + maxBorrowableAmountFromVault,
totalBorrows,
totalReserves,
deltaTime,
fullUtilizationRate
);
return ratePerBlock;
}
/**
* @notice Returns the current per-block supply interest rate for this cToken
* @return The supply interest rate per block, scaled by 1e18
*/
function supplyRatePerBlock() external view override returns (uint) {
// NUMALENDING
// supply rate is based on lending contract cash & vault available to borrow
uint maxBorrowableAmountFromVault;
if (address(vault) != address(0))
maxBorrowableAmountFromVault = vault.getMaxBorrow();
uint currentTimestamp = block.timestamp;
uint timestampPrior = accrualBlockTimestamp;
uint deltaTime = currentTimestamp - timestampPrior;
return
interestRateModel.getSupplyRate(
getCashPrior() + maxBorrowableAmountFromVault,
totalBorrows,
totalReserves,
reserveFactorMantissa,
deltaTime,
fullUtilizationRate
);
}
/**
* @notice Applies accrued interest to total borrows and reserves
* @dev This calculates interest accrued from the last checkpointed block
* up to the current block and writes new checkpoint to storage.
*/
function accrueInterest() public virtual override returns (uint) {
/* Remember the initial block number */
uint currentBlockNumber = getBlockNumber();
uint accrualBlockNumberPrior = accrualBlockNumber;
/* Short-circuit accumulating 0 interest */
if (accrualBlockNumberPrior == currentBlockNumber) {
return NO_ERROR;
}
/* Read the previous values out of storage */
// NUMALENDING
// interest rate is based on lending contract cash & vault available to borrow
uint maxBorrowableAmountFromVault;
if (address(vault) != address(0))
maxBorrowableAmountFromVault = vault.getMaxBorrow();
uint cashPrior = getCashPrior() + maxBorrowableAmountFromVault;
uint borrowsPrior = totalBorrows;
uint reservesPrior = totalReserves;
uint borrowIndexPrior = borrowIndex;
/* Calculate the current borrow interest rate */
(
uint borrowRateMantissa,
uint newfullUtilizationRate
) = interestRateModel.getBorrowRate(
cashPrior,
borrowsPrior,
reservesPrior,
block.timestamp - accrualBlockTimestamp,
fullUtilizationRate
);
require(
borrowRateMantissa <= borrowRateMaxMantissa,
"borrow rate is absurdly high"
);
/* Calculate the number of blocks elapsed since the last accrual */
uint blockDelta = currentBlockNumber - accrualBlockNumberPrior;
/*
* Calculate the interest accumulated into borrows and reserves and the new index:
* simpleInterestFactor = borrowRate * blockDelta
* interestAccumulated = simpleInterestFactor * totalBorrows
* totalBorrowsNew = interestAccumulated + totalBorrows
* totalReservesNew = interestAccumulated * reserveFactor + totalReserves
* borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex
*/
Exp memory simpleInterestFactor = mul_(
Exp({mantissa: borrowRateMantissa}),
blockDelta
);
uint interestAccumulated = mul_ScalarTruncate(
simpleInterestFactor,
borrowsPrior
);
uint totalBorrowsNew = interestAccumulated + borrowsPrior;
uint totalReservesNew = mul_ScalarTruncateAddUInt(
Exp({mantissa: reserveFactorMantissa}),
interestAccumulated,
reservesPrior
);
uint borrowIndexNew = mul_ScalarTruncateAddUInt(
simpleInterestFactor,
borrowIndexPrior,
borrowIndexPrior
);
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/* We write the previously calculated values into storage */
accrualBlockNumber = currentBlockNumber;
accrualBlockTimestamp = block.timestamp;
borrowIndex = borrowIndexNew;
totalBorrows = totalBorrowsNew;
totalReserves = totalReservesNew;
if (fullUtilizationRate != newfullUtilizationRate) {
emit UpdateRate(fullUtilizationRate, newfullUtilizationRate);
fullUtilizationRate = newfullUtilizationRate;
}
/* We emit an AccrueInterest event */
emit AccrueInterest(
cashPrior,
interestAccumulated,
borrowIndexNew,
totalBorrowsNew
);
return NO_ERROR;
}
function borrowFreshNoTransfer(
address payable borrower,
uint borrowAmount
) internal virtual override {
/* Fail if borrow not allowed */
uint allowed = comptroller.borrowAllowed(
address(this),
borrower,
borrowAmount
);
if (allowed != 0) {
revert BorrowComptrollerRejection(allowed);
}
/* Verify market's block number equals current block number */
if (accrualBlockNumber != getBlockNumber()) {
revert BorrowFreshnessCheck();
}
/* Fail gracefully if protocol has insufficient underlying cash */
uint cashPrior = getCashPrior();
if (cashPrior < borrowAmount) {
// not enough cash in lending contract, check if we can get some from the vault
// NUMALENDING
//
if (address(vault) != address(0)) {
uint amountNeeded = borrowAmount - cashPrior;
uint maxBorrowableAmountFromVault = vault.getMaxBorrow();
if (amountNeeded <= maxBorrowableAmountFromVault) {
// if ok, borrow from vault
vault.borrow(amountNeeded);
} else {
// not enough in vault
revert BorrowCashNotAvailable();
}
} else {
revert BorrowCashNotAvailable();
}
}
/*
* We calculate the new borrower and total borrow balances, failing on overflow:
* accountBorrowNew = accountBorrow + borrowAmount
* totalBorrowsNew = totalBorrows + borrowAmount
*/
uint accountBorrowsPrev = borrowBalanceStoredInternal(borrower);
uint accountBorrowsNew = accountBorrowsPrev + borrowAmount;
uint totalBorrowsNew = totalBorrows + borrowAmount;
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We write the previously calculated values into storage.
* Note: Avoid token reentrancy attacks by writing increased borrow before external transfer.
`*/
accountBorrows[borrower].principal = accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = totalBorrowsNew;
/* We emit a Borrow event */
emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew);
}
/**
* @notice Borrows are repaid by another user (possibly the borrower).
* @param payer the account paying off the borrow
* @param borrower the account with the debt being payed off
* @param repayAmount the amount of underlying tokens being returned, or -1 for the full outstanding amount
* @return (uint) the actual repayment amount.
*/
function repayBorrowFresh(
address payer,
address borrower,
uint repayAmount
) internal override returns (uint) {
uint actualRepayAmount = CToken.repayBorrowFresh(
payer,
borrower,
repayAmount
);
// NUMALENDING
// if we have debt from the vault, repay vault first
uint vaultDebt = vault.getDebt();
if (vaultDebt > 0) {
uint repayToVault = vaultDebt;
if (actualRepayAmount <= vaultDebt) {
repayToVault = actualRepayAmount;
}
// repay vault debt
EIP20Interface(underlying).approve(address(vault), repayToVault);
vault.repay(repayToVault);
}
return actualRepayAmount;
}
/**
* @notice User redeems cTokens in exchange for the underlying asset
* @dev Assumes interest has already been accrued up to the current block
* @param redeemer The address of the account which is redeeming the tokens
* @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero)
* @param redeemAmountIn The number of underlying tokens to receive from redeeming cTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero)
*/
function redeemFresh(
address payable redeemer,
uint redeemTokensIn,
uint redeemAmountIn
) internal override {
require(
redeemTokensIn == 0 || redeemAmountIn == 0,
"one of redeemTokensIn or redeemAmountIn must be zero"
);
/* exchangeRate = invoke Exchange Rate Stored() */
Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal()});
uint redeemTokens;
uint redeemAmount;
/* If redeemTokensIn > 0: */
if (redeemTokensIn > 0) {
/*
* We calculate the exchange rate and the amount of underlying to be redeemed:
* redeemTokens = redeemTokensIn
* redeemAmount = redeemTokensIn x exchangeRateCurrent
*/
redeemTokens = redeemTokensIn;
redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokensIn);
} else {
/*
* We get the current exchange rate and calculate the amount to be redeemed:
* redeemTokens = redeemAmountIn / exchangeRate
* redeemAmount = redeemAmountIn
*/
redeemTokens = div_(redeemAmountIn, exchangeRate);
redeemAmount = redeemAmountIn;
// // NUMALENDING
// // this was not in original compound code but I think it's necessary
// // because due to exchange rate we might ask for more underlying tokens than equvalent in cTokens
// // for example X + 500 000 000 wei is equivalent to X
// redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokens);
}
/* Fail if redeem not allowed */
uint allowed = comptroller.redeemAllowed(
address(this),
redeemer,
redeemTokens
);
if (allowed != 0) {
revert RedeemComptrollerRejection(allowed);
}
/* Verify market's block number equals current block number */
if (accrualBlockNumber != getBlockNumber()) {
revert RedeemFreshnessCheck();
}
if (getCashPrior() < redeemAmount) {
// NUMALENDING
// try to redeem from vault
uint amountNeeded = redeemAmount - getCashPrior();
uint maxBorrowableAmountFromVault;
if (address(vault) != address(0))
maxBorrowableAmountFromVault = vault.getMaxBorrow();
if (amountNeeded <= maxBorrowableAmountFromVault) {
// if ok, borrow from vault
vault.borrow(amountNeeded);
} else {
revert RedeemTransferOutNotPossible();
}
}
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/*
* We write the previously calculated values into storage.
* Note: Avoid token reentrancy attacks by writing reduced supply before external transfer.
*/
totalSupply = totalSupply - redeemTokens;
accountTokens[redeemer] = accountTokens[redeemer] - redeemTokens;
/*
* We invoke doTransferOut for the redeemer and the redeemAmount.
* Note: The cToken must handle variations between ERC-20 and ETH underlying.
* On success, the cToken has redeemAmount less of cash.
* doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred.
*/
doTransferOut(redeemer, redeemAmount);
/* We emit a Transfer event, and a Redeem event */
emit Transfer(redeemer, address(this), redeemTokens);
emit Redeem(redeemer, redeemAmount, redeemTokens);
/* We call the defense hook */
comptroller.redeemVerify(
address(this),
redeemer,
redeemAmount,
redeemTokens
);
}
/**
* @notice Calculates the exchange rate from the underlying to the CToken
* @dev This function does not accrue interest before calculating the exchange rate
* @return calculated exchange rate scaled by 1e18
*/
function exchangeRateStoredInternal()
internal
view
override
returns (uint)
{
uint _totalSupply = totalSupply;
if (_totalSupply == 0) {
/*
* If there are no tokens minted:
* exchangeRate = initialExchangeRate
*/
return initialExchangeRateMantissa;
} else {
/*
* Otherwise:
* exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply
*/
uint totalCash = getCashPrior();
// NUMALENDING
// vault debt does not count for exchange rate
uint vaultDebt = vault.getDebt();
uint cashPlusBorrowsMinusReserves = totalCash +
totalBorrows -
vaultDebt -
totalReserves;
uint exchangeRate = (cashPlusBorrowsMinusReserves * expScale) /
_totalSupply;
return exchangeRate;
}
}
}