This repository has been archived by the owner on Jan 22, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathFraxlendPairCore.sol
1385 lines (1178 loc) · 68 KB
/
FraxlendPairCore.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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;
// ====================================================================
// | ______ _______ |
// | / _____________ __ __ / ____(_____ ____ _____ ________ |
// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |
// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |
// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |
// | |
// ====================================================================
// ========================= FraxlendPairCore =========================
// ====================================================================
// Frax Finance: https://github.com/FraxFinance
// Primary Author
// Drake Evans: https://github.com/DrakeEvans
// Reviewers
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian
// Travis Moore: https://github.com/FortisFortuna
// Jack Corddry: https://github.com/corddry
// Rich Gee: https://github.com/zer0blockchain
// ====================================================================
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {FraxlendPairAccessControl} from "./FraxlendPairAccessControl.sol";
import {FraxlendPairConstants} from "./FraxlendPairConstants.sol";
import {VaultAccount, VaultAccountingLibrary} from "./libraries/VaultAccount.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {IDualOracle} from "./interfaces/IDualOracle.sol";
import {IRateCalculatorV2} from "./interfaces/IRateCalculatorV2.sol";
import {ISwapper} from "./interfaces/ISwapper.sol";
/// @title FraxlendPairCore
/// @author Drake Evans (Frax Finance) https://github.com/drakeevans
/// @notice An abstract contract which contains the core logic and storage for the FraxlendPair
abstract contract FraxlendPairCore is FraxlendPairAccessControl, FraxlendPairConstants, ERC20, ReentrancyGuard {
using VaultAccountingLibrary for VaultAccount;
using SafeERC20 for IERC20;
using SafeCast for uint256;
function version() external pure returns (uint256 _major, uint256 _minor, uint256 _patch) {
_major = 3;
_minor = 0;
_patch = 0;
}
// ============================================================================================
// Settings set by constructor()
// ============================================================================================
// Asset and collateral contracts
IERC20 internal immutable assetContract;
IERC20 public immutable collateralContract;
// LTV Settings
/// @notice The maximum LTV allowed for this pair
/// @dev 1e5 precision
uint256 public maxLTV;
// Liquidation Fees
/// @notice The liquidation fee, given as a % of repayment amount, when all collateral is consumed in liquidation
/// @dev 1e5 precision
uint256 public cleanLiquidationFee;
/// @notice The liquidation fee, given as % of repayment amount, when some collateral remains for borrower
/// @dev 1e5 precision
uint256 public dirtyLiquidationFee;
/// @notice The minimum amount of collateral required to leave upon dirty liquidation
uint256 public minCollateralRequiredOnDirtyLiquidation;
/// @notice The portion of the liquidation fee given to protocol
/// @dev 1e5 precision
uint256 public protocolLiquidationFee;
// Interest Rate Calculator Contract
IRateCalculatorV2 public rateContract; // For complex rate calculations
// Swapper
mapping(address => bool) public swappers; // approved swapper addresses
// If provided, will only update interest rate when external add interest is called if this threshold is met from previous UR update
uint256 public minURChangeForExternalAddInterest = UTIL_PREC / 1000;
// ERC20 Metadata
string internal nameOfContract;
string internal symbolOfContract;
uint8 internal immutable decimalsOfContract;
// ============================================================================================
// Storage
// ============================================================================================
/// @notice Stores information about the current interest rate
/// @dev struct is packed to reduce SLOADs. feeToProtocolRate is 1e5 precision, ratePerSec & fullUtilizationRate is 1e18 precision
CurrentRateInfo public currentRateInfo;
struct CurrentRateInfo {
uint32 lastBlock;
uint32 feeToProtocolRate; // Fee amount 1e5 precision
uint64 lastTimestamp;
uint64 ratePerSec;
uint64 fullUtilizationRate;
}
/// @notice Stores information about the current exchange rate. Collateral:Asset ratio
/// @dev Struct packed to save SLOADs. Amount of Collateral Token to buy 1e18 Asset Token
ExchangeRateInfo public exchangeRateInfo;
struct ExchangeRateInfo {
address oracle;
uint32 maxOracleDeviation; // % of larger number, 1e5 precision
uint184 lastTimestamp;
uint256 lowExchangeRate;
uint256 highExchangeRate;
}
// Contract Level Accounting
VaultAccount public totalAsset; // amount = total amount of assets, shares = total shares outstanding
VaultAccount public totalBorrow; // amount = total borrow amount with interest accrued, shares = total shares outstanding
uint256 public totalCollateral; // total amount of collateral in contract
// User Level Accounting
/// @notice Stores the balance of collateral for each user
mapping(address => uint256) public userCollateralBalance; // amount of collateral each user is backed
/// @notice Stores the balance of borrow shares for each user
mapping(address => uint256) public userBorrowShares; // represents the shares held by individuals
uint256 _prevUtilizationRate;
// NOTE: user shares of assets are represented as ERC-20 tokens and accessible via balanceOf()
// ============================================================================================
// Constructor
// ============================================================================================
/// @notice The ```constructor``` function is called on deployment
/// @param _configData abi.encode(address _asset, address _collateral, address _oracle, uint32 _maxOracleDeviation, address _rateContract, uint64 _fullUtilizationRate, uint256 _maxLTV, uint256 _cleanLiquidationFee, uint256 _dirtyLiquidationFee, uint256 _protocolLiquidationFee)
/// @param _immutables abi.encode(address _circuitBreakerAddress, address _comptrollerAddress, address _timelockAddress)
/// @param _customConfigData abi.encode(string memory _nameOfContract, string memory _symbolOfContract, uint8 _decimalsOfContract)
constructor(bytes memory _configData, bytes memory _immutables, bytes memory _customConfigData)
FraxlendPairAccessControl(_immutables)
ERC20("", "")
{
{
(
address _asset,
address _collateral,
address _oracle,
uint32 _maxOracleDeviation,
address _rateContract,
uint64 _fullUtilizationRate,
uint256 _maxLTV,
uint256 _liquidationFee,
uint256 _protocolLiquidationFee
) = abi.decode(_configData, (address, address, address, uint32, address, uint64, uint256, uint256, uint256));
// Pair Settings
assetContract = IERC20(_asset);
collateralContract = IERC20(_collateral);
currentRateInfo.feeToProtocolRate = 0;
currentRateInfo.fullUtilizationRate = _fullUtilizationRate;
currentRateInfo.lastTimestamp = uint64(block.timestamp - 1);
currentRateInfo.lastBlock = uint32(block.number - 1);
exchangeRateInfo.oracle = _oracle;
exchangeRateInfo.maxOracleDeviation = _maxOracleDeviation;
rateContract = IRateCalculatorV2(_rateContract);
//Liquidation Fee Settings
cleanLiquidationFee = _liquidationFee;
dirtyLiquidationFee = (_liquidationFee * 90_000) / LIQ_PRECISION; // 90% of clean fee
protocolLiquidationFee = _protocolLiquidationFee;
// set maxLTV
maxLTV = _maxLTV;
}
{
(string memory _nameOfContract, string memory _symbolOfContract, uint8 _decimalsOfContract) =
abi.decode(_customConfigData, (string, string, uint8));
// ERC20 Metadata
nameOfContract = _nameOfContract;
symbolOfContract = _symbolOfContract;
decimalsOfContract = _decimalsOfContract;
// Instantiate Interest
_addInterest();
// Instantiate Exchange Rate
_updateExchangeRate();
}
}
// ============================================================================================
// Internal Helpers
// ============================================================================================
/// @notice The ```_totalAssetAvailable``` function returns the total balance of Asset Tokens in the contract
/// @param _totalAsset VaultAccount struct which stores total amount and shares for assets
/// @param _totalBorrow VaultAccount struct which stores total amount and shares for borrows
/// @param _includeVault Whether to include assets from the external asset vault, if configured in total available
/// @return The balance of Asset Tokens held by contract
function _totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow, bool _includeVault)
internal
view
returns (uint256)
{
if (_includeVault) {
return _totalAsset.totalAmount(address(externalAssetVault)) - _totalBorrow.amount;
}
return _totalAsset.amount - _totalBorrow.amount;
}
/// @notice The ```_isSolvent``` function determines if a given borrower is solvent given an exchange rate
/// @param _borrower The borrower address to check
/// @param _exchangeRate The exchange rate, i.e. the amount of collateral to buy 1e18 asset
/// @return Whether borrower is solvent
function _isSolvent(address _borrower, uint256 _exchangeRate) internal view returns (bool) {
if (maxLTV == 0) return true;
uint256 _borrowerAmount = totalBorrow.toAmount(userBorrowShares[_borrower], true);
if (_borrowerAmount == 0) return true;
uint256 _collateralAmount = userCollateralBalance[_borrower];
if (_collateralAmount == 0) return false;
uint256 _ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
return _ltv <= maxLTV;
}
// ============================================================================================
// Modifiers
// ============================================================================================
/// @notice Checks for solvency AFTER executing contract code
/// @param _borrower The borrower whose solvency we will check
modifier isSolvent(address _borrower) {
_;
ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
if (!_isSolvent(_borrower, _exchangeRateInfo.highExchangeRate)) {
revert Insolvent(
totalBorrow.toAmount(userBorrowShares[_borrower], true),
userCollateralBalance[_borrower],
_exchangeRateInfo.highExchangeRate
);
}
}
// ============================================================================================
// Functions: Interest Accumulation and Adjustment
// ============================================================================================
/// @notice The ```AddInterest``` event is emitted when interest is accrued by borrowers
/// @param interestEarned The total interest accrued by all borrowers
/// @param rate The interest rate used to calculate accrued interest
/// @param feesAmount The amount of fees paid to protocol
/// @param feesShare The amount of shares distributed to protocol
event AddInterest(uint256 interestEarned, uint256 rate, uint256 feesAmount, uint256 feesShare);
/// @notice The ```SkipAddingInterest``` event is emitted when external add interest is called byt noops due to small rate change
/// @param rateChange The rate change
event SkipAddingInterest(uint256 rateChange);
/// @notice The ```UpdateRate``` event is emitted when the interest rate is updated
/// @param oldRatePerSec The old interest rate (per second)
/// @param oldFullUtilizationRate The old full utilization rate
/// @param newRatePerSec The new interest rate (per second)
/// @param newFullUtilizationRate The new full utilization rate
event UpdateRate(
uint256 oldRatePerSec, uint256 oldFullUtilizationRate, uint256 newRatePerSec, uint256 newFullUtilizationRate
);
/// @notice The ```addInterest``` function is a public implementation of _addInterest and allows 3rd parties to trigger interest accrual
/// @return _interestEarned The amount of interest accrued by all borrowers
/// @return _feesAmount The amount of fees paid to protocol
/// @return _feesShare The amount of shares distributed to protocol
/// @return _currentRateInfo The new rate info struct
/// @return _totalAsset The new total asset struct
/// @return _totalBorrow The new total borrow struct
function addInterest(bool _returnAccounting)
external
nonReentrant
returns (
uint256 _interestEarned,
uint256 _feesAmount,
uint256 _feesShare,
CurrentRateInfo memory _currentRateInfo,
VaultAccount memory _totalAsset,
VaultAccount memory _totalBorrow
)
{
_currentRateInfo = currentRateInfo;
// the following checks whether the current utilization rate against the new utilization rate
// (including external assets available) exceeds a threshold and only updates interest if so.
// With this enabled, it's obviously possible for there to be some level of "unfair" interest
// paid by borrowers and/or earned by suppliers, but the idea is this unfairness
// should theoretically be negligible within some level of error and therefore it won't matter.
// This is in place to support lower gas & more arbitrage volume through the pair since arbitrage
// would many times only occur with small changes in asset supply or borrowed positions.
uint256 _currentUtilizationRate = _prevUtilizationRate;
uint256 _totalAssetsAvailable = totalAsset.totalAmount(address(externalAssetVault));
uint256 _newUtilizationRate =
_totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
uint256 _rateChange = _newUtilizationRate > _currentUtilizationRate
? _newUtilizationRate - _currentUtilizationRate
: _currentUtilizationRate - _newUtilizationRate;
if (
_currentUtilizationRate != 0
&& _rateChange < _currentUtilizationRate * minURChangeForExternalAddInterest / UTIL_PREC
) {
emit SkipAddingInterest(_rateChange);
} else {
(, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest();
}
if (_returnAccounting) {
_totalAsset = totalAsset;
_totalBorrow = totalBorrow;
}
}
/// @notice The ```previewAddInterest``` function
/// @return _interestEarned The amount of interest accrued by all borrowers
/// @return _feesAmount The amount of fees paid to protocol
/// @return _feesShare The amount of shares distributed to protocol
/// @return _newCurrentRateInfo The new rate info struct
/// @return _totalAsset The new total asset struct
/// @return _totalBorrow The new total borrow struct
function previewAddInterest()
public
view
returns (
uint256 _interestEarned,
uint256 _feesAmount,
uint256 _feesShare,
CurrentRateInfo memory _newCurrentRateInfo,
VaultAccount memory _totalAsset,
VaultAccount memory _totalBorrow
)
{
_newCurrentRateInfo = currentRateInfo;
// Write return values
InterestCalculationResults memory _results = _calculateInterest(_newCurrentRateInfo);
if (_results.isInterestUpdated) {
_interestEarned = _results.interestEarned;
_feesAmount = _results.feesAmount;
_feesShare = _results.feesShare;
_newCurrentRateInfo.ratePerSec = _results.newRate;
_newCurrentRateInfo.fullUtilizationRate = _results.newFullUtilizationRate;
_totalAsset = _results.totalAsset;
_totalBorrow = _results.totalBorrow;
} else {
_totalAsset = totalAsset;
_totalBorrow = totalBorrow;
}
}
struct InterestCalculationResults {
bool isInterestUpdated;
uint64 newRate;
uint64 newFullUtilizationRate;
uint256 interestEarned;
uint256 feesAmount;
uint256 feesShare;
VaultAccount totalAsset;
VaultAccount totalBorrow;
}
/// @notice The ```_calculateInterest``` function calculates the interest to be accrued and the new interest rate info
/// @param _currentRateInfo The current rate info
/// @return _results The results of the interest calculation
function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
internal
view
returns (InterestCalculationResults memory _results)
{
// Short circuit if interest already calculated this block OR if interest is paused
if (_currentRateInfo.lastTimestamp != block.timestamp && !isInterestPaused) {
// Indicate that interest is updated and calculated
_results.isInterestUpdated = true;
// Write return values and use these to save gas
_results.totalAsset = totalAsset;
_results.totalBorrow = totalBorrow;
// Time elapsed since last interest update
uint256 _deltaTime = block.timestamp - _currentRateInfo.lastTimestamp;
// Total assets available including what resides in the external vault
uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));
// Get the utilization rate
uint256 _utilizationRate =
_totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;
// Request new interest rate and full utilization rate from the rate calculator
(_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
_deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
);
// Calculate interest accrued
_results.interestEarned = (_deltaTime * _results.totalBorrow.amount * _results.newRate) / RATE_PRECISION;
// Accrue interest (if any) and fees iff no overflow
if (
_results.interestEarned > 0
&& _results.interestEarned + _results.totalBorrow.amount <= type(uint128).max
&& _results.interestEarned + _totalAssetsAvailable <= type(uint128).max
) {
// Increment totalBorrow and totalAsset by interestEarned
_results.totalBorrow.amount += _results.interestEarned.toUint128();
_results.totalAsset.amount += _results.interestEarned.toUint128();
if (_currentRateInfo.feeToProtocolRate > 0) {
_results.feesAmount = (_results.interestEarned * _currentRateInfo.feeToProtocolRate) / FEE_PRECISION;
_results.feesShare = (_results.feesAmount * _results.totalAsset.shares)
/ (_results.totalAsset.totalAmount(address(0)) - _results.feesAmount);
// Effects: Give new shares to this contract, effectively diluting lenders an amount equal to the fees
// We can safely cast because _feesShare < _feesAmount < interestEarned which is always less than uint128
_results.totalAsset.shares += _results.feesShare.toUint128();
}
}
}
}
/// @notice The ```_addInterest``` function is invoked prior to every external function and is used to accrue interest and update interest rate
/// @dev Can only called once per block
/// @return _isInterestUpdated True if interest was calculated
/// @return _interestEarned The amount of interest accrued by all borrowers
/// @return _feesAmount The amount of fees paid to protocol
/// @return _feesShare The amount of shares distributed to protocol
/// @return _currentRateInfo The new rate info struct
function _addInterest()
internal
returns (
bool _isInterestUpdated,
uint256 _interestEarned,
uint256 _feesAmount,
uint256 _feesShare,
CurrentRateInfo memory _currentRateInfo
)
{
// Pull from storage and set default return values
_currentRateInfo = currentRateInfo;
// store the current utilization rate as previous for next check
uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, totalBorrow, true);
_prevUtilizationRate = _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * totalBorrow.amount) / _totalAssetsAvailable;
// Calc interest
InterestCalculationResults memory _results = _calculateInterest(_currentRateInfo);
// Write return values only if interest was updated and calculated
if (_results.isInterestUpdated) {
_isInterestUpdated = _results.isInterestUpdated;
_interestEarned = _results.interestEarned;
_feesAmount = _results.feesAmount;
_feesShare = _results.feesShare;
// emit here so that we have access to the old values
emit UpdateRate(
_currentRateInfo.ratePerSec,
_currentRateInfo.fullUtilizationRate,
_results.newRate,
_results.newFullUtilizationRate
);
emit AddInterest(_interestEarned, _results.newRate, _feesAmount, _feesShare);
// overwrite original values
_currentRateInfo.ratePerSec = _results.newRate;
_currentRateInfo.fullUtilizationRate = _results.newFullUtilizationRate;
_currentRateInfo.lastTimestamp = uint64(block.timestamp);
_currentRateInfo.lastBlock = uint32(block.number);
// Effects: write to state
currentRateInfo = _currentRateInfo;
totalAsset = _results.totalAsset;
totalBorrow = _results.totalBorrow;
if (_feesShare > 0) _mint(address(this), _feesShare);
}
}
// ============================================================================================
// Functions: ExchangeRate
// ============================================================================================
/// @notice The ```UpdateExchangeRate``` event is emitted when the Collateral:Asset exchange rate is updated
/// @param lowExchangeRate The low exchange rate
/// @param highExchangeRate The high exchange rate
event UpdateExchangeRate(uint256 lowExchangeRate, uint256 highExchangeRate);
/// @notice The ```WarnOracleData``` event is emitted when one of the oracles has stale or otherwise problematic data
/// @param oracle The oracle address
event WarnOracleData(address oracle);
/// @notice The ```updateExchangeRate``` function is the external implementation of _updateExchangeRate.
/// @dev This function is invoked at most once per block as these queries can be expensive
/// @return _isBorrowAllowed True if deviation is within bounds
/// @return _lowExchangeRate The low exchange rate
/// @return _highExchangeRate The high exchange rate
function updateExchangeRate()
external
nonReentrant
returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate)
{
return _updateExchangeRate();
}
/// @notice The ```_updateExchangeRate``` function retrieves the latest exchange rate. i.e how much collateral to buy 1e18 asset.
/// @dev This function is invoked at most once per block as these queries can be expensive
/// @return _isBorrowAllowed True if deviation is within bounds
/// @return _lowExchangeRate The low exchange rate
/// @return _highExchangeRate The high exchange rate
function _updateExchangeRate()
internal
returns (bool _isBorrowAllowed, uint256 _lowExchangeRate, uint256 _highExchangeRate)
{
// Pull from storage to save gas and set default return values
ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
// Short circuit if already updated this block
if (_exchangeRateInfo.lastTimestamp != block.timestamp) {
// Get the latest exchange rate from the dual oracle
bool _oneOracleBad;
(_oneOracleBad, _lowExchangeRate, _highExchangeRate) = IDualOracle(_exchangeRateInfo.oracle).getPrices();
// If one oracle is bad data, emit an event for off-chain monitoring
if (_oneOracleBad) emit WarnOracleData(_exchangeRateInfo.oracle);
// Effects: Bookkeeping and write to storage
_exchangeRateInfo.lastTimestamp = uint184(block.timestamp);
_exchangeRateInfo.lowExchangeRate = _lowExchangeRate;
_exchangeRateInfo.highExchangeRate = _highExchangeRate;
exchangeRateInfo = _exchangeRateInfo;
emit UpdateExchangeRate(_lowExchangeRate, _highExchangeRate);
} else {
// Use default return values if already updated this block
_lowExchangeRate = _exchangeRateInfo.lowExchangeRate;
_highExchangeRate = _exchangeRateInfo.highExchangeRate;
}
uint256 _deviation = (
DEVIATION_PRECISION * (_exchangeRateInfo.highExchangeRate - _exchangeRateInfo.lowExchangeRate)
) / _exchangeRateInfo.highExchangeRate;
if (_deviation <= _exchangeRateInfo.maxOracleDeviation) {
_isBorrowAllowed = true;
}
}
// ============================================================================================
// Functions: Lending
// ============================================================================================
/// @notice The ```Deposit``` event fires when a user deposits assets to the pair
/// @param caller the msg.sender
/// @param owner the account the fTokens are sent to
/// @param assets the amount of assets deposited
/// @param shares the number of fTokens minted
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
/// @notice The ```_deposit``` function is the internal implementation for lending assets
/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function
/// @param _totalAsset An in memory VaultAccount struct representing the total amounts and shares for the Asset Token
/// @param _amount The amount of Asset Token to be transferred
/// @param _shares The amount of Asset Shares (fTokens) to be minted
/// @param _receiver The address to receive the Asset Shares (fTokens)
/// @param _shouldTransfer Whether asset tokens should be deposited from the sender
function _deposit(
VaultAccount memory _totalAsset,
uint128 _amount,
uint128 _shares,
address _receiver,
bool _shouldTransfer
) internal {
// Effects: bookkeeping
_totalAsset.amount += _amount;
_totalAsset.shares += _shares;
// Effects: write back to storage
_mint(_receiver, _shares);
totalAsset = _totalAsset;
// Interactions
if (_shouldTransfer) {
assetContract.safeTransferFrom(msg.sender, address(this), _amount);
if (address(externalAssetVault) != address(0) && _amount > _totalAsset.totalAmount(address(0)) / 1000) {
// if the external asset vault is over utilized or this pair is over allocated,
// return the amount being deposited to the vault
externalAssetVault.whitelistUpdate(true);
uint256 _assetsUtilized = externalAssetVault.vaultUtilization(address(this));
bool _vaultOverUtilized =
1e18 * externalAssetVault.totalAssetsUtilized() / externalAssetVault.totalAssets() > 1e18 * 8 / 10;
bool _pairOverAllocation = _assetsUtilized > externalAssetVault.vaultMaxAllocation(address(this));
if (_vaultOverUtilized || _pairOverAllocation) {
uint256 _extAmount = _assetsUtilized > _amount ? _amount : _assetsUtilized;
_withdrawToVault(_extAmount);
}
}
}
emit Deposit(msg.sender, _receiver, _amount, _shares);
}
function previewDeposit(uint256 _assets) external view returns (uint256 _sharesReceived) {
(,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
_sharesReceived = _totalAsset.toShares(_assets, false);
}
/// @notice The ```deposit``` function allows a user to Lend Assets by specifying the amount of Asset Tokens to lend
/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function
/// @param _amount The amount of Asset Token to transfer to Pair
/// @param _receiver The address to receive the Asset Shares (fTokens)
/// @return _sharesReceived The number of fTokens received for the deposit
function deposit(uint256 _amount, address _receiver) external nonReentrant returns (uint256 _sharesReceived) {
if (_receiver == address(0)) revert InvalidReceiver();
// Accrue interest if necessary
_addInterest();
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Check if this deposit will violate the deposit limit
if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();
// Calculate the number of fTokens to mint
_sharesReceived = _totalAsset.toShares(_amount, false);
// Execute the deposit effects
_deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver, true);
}
/// @notice The ```_depositFromVault``` function deposits assets here from the configured external vault if available
/// @param _amount The amount of Asset Tokens to be transferred from the vault
/// @return _sharesReceived The number of Asset Shares (fTokens) to mint for Asset Tokens
function _depositFromVault(uint256 _amount) internal returns (uint256 _sharesReceived) {
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Calculate the number of fTokens to mint
_sharesReceived = _totalAsset.toShares(_amount, false);
// Withdraw assets from external vault here
externalAssetVault.whitelistWithdraw(_amount);
// Execute the deposit effects
_deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), address(externalAssetVault), false);
}
/// @notice The ```_withdrawToVault``` function withdraws assets back to an external vault if previously used
/// @param _amountToReturn The amount of Asset Tokens to be transferred to the vault
/// @return _shares The number of Asset Shares (fTokens) to burn for Asset Tokens
function _withdrawToVault(uint256 _amountToReturn) internal returns (uint256 _shares) {
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Calculate the number of shares to burn based on the assets to transfer
_shares = _totalAsset.toShares(_amountToReturn, true);
uint256 _vaultBal = balanceOf(address(externalAssetVault));
_shares = _vaultBal < _shares ? _vaultBal : _shares;
// Deposit assets to external vault
assetContract.approve(address(externalAssetVault), _amountToReturn);
externalAssetVault.whitelistDeposit(_amountToReturn);
// Execute the withdraw effects for vault
// receive assets here in order to call whitelistDeposit and handle accounting in external vault
_redeem(
_totalAsset,
_amountToReturn.toUint128(),
_shares.toUint128(),
address(this),
address(externalAssetVault),
true
);
}
function previewMint(uint256 _shares) external view returns (uint256 _amount) {
(,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
_amount = _totalAsset.toAmount(_shares, true);
}
function mint(uint256 _shares, address _receiver) external nonReentrant returns (uint256 _amount) {
if (_receiver == address(0)) revert InvalidReceiver();
// Accrue interest if necessary
_addInterest();
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Calculate the number of assets to transfer based on the shares to mint
_amount = _totalAsset.toAmount(_shares, true);
// Check if this deposit will violate the deposit limit
if (depositLimit < _totalAsset.totalAmount(address(0)) + _amount) revert ExceedsDepositLimit();
// Execute the deposit effects
_deposit(_totalAsset, _amount.toUint128(), _shares.toUint128(), _receiver, true);
}
/// @notice The ```Withdraw``` event fires when a user redeems their fTokens for the underlying asset
/// @param caller the msg.sender
/// @param receiver The address to which the underlying asset will be transferred to
/// @param owner The owner of the fTokens
/// @param assets The assets transferred
/// @param shares The number of fTokens burned
event Withdraw(
address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
);
/// @notice The ```_redeem``` function is an internal implementation which allows a Lender to pull their Asset Tokens out of the Pair
/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function
/// @param _totalAsset An in-memory VaultAccount struct which holds the total amount of Asset Tokens and the total number of Asset Shares (fTokens)
/// @param _amountToReturn The number of Asset Tokens to return
/// @param _shares The number of Asset Shares (fTokens) to burn
/// @param _receiver The address to which the Asset Tokens will be transferred
/// @param _owner The owner of the Asset Shares (fTokens)
function _redeem(
VaultAccount memory _totalAsset,
uint128 _amountToReturn,
uint128 _shares,
address _receiver,
address _owner,
bool _skipAllowanceCheck
) internal {
// Check for sufficient allowance/approval if necessary
if (msg.sender != _owner && !_skipAllowanceCheck) {
uint256 allowed = allowance(_owner, msg.sender);
// NOTE: This will revert on underflow ensuring that allowance > shares
if (allowed != type(uint256).max) _approve(_owner, msg.sender, allowed - _shares);
}
// Check for sufficient withdraw liquidity (not strictly necessary because balance will underflow)
uint256 _totAssetsAvailable = _totalAssetAvailable(_totalAsset, totalBorrow, true);
if (_totAssetsAvailable < _amountToReturn) {
revert InsufficientAssetsInContract(_totAssetsAvailable, _amountToReturn);
}
// If we're redeeming back to the vault, don't deposit from the vault
if (_owner != address(externalAssetVault)) {
uint256 _localAssetsAvailable = _totalAssetAvailable(_totalAsset, totalBorrow, false);
if (_localAssetsAvailable < _amountToReturn) {
uint256 _vaultAmt = _amountToReturn - _localAssetsAvailable;
_depositFromVault(_vaultAmt);
// Rewrite to memory, now it's the latest value!
_totalAsset = totalAsset;
}
}
// Effects: bookkeeping
_totalAsset.amount -= _amountToReturn;
_totalAsset.shares -= _shares;
// Effects: write to storage
totalAsset = _totalAsset;
_burn(_owner, _shares);
// Interactions
if (_receiver != address(this)) {
assetContract.safeTransfer(_receiver, _amountToReturn);
}
emit Withdraw(msg.sender, _receiver, _owner, _amountToReturn, _shares);
}
function previewRedeem(uint256 _shares) external view returns (uint256 _assets) {
(,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
_assets = _totalAsset.toAmount(_shares, false);
}
/// @notice The ```redeem``` function allows the caller to redeem their Asset Shares for Asset Tokens
/// @param _shares The number of Asset Shares (fTokens) to burn for Asset Tokens
/// @param _receiver The address to which the Asset Tokens will be transferred
/// @param _owner The owner of the Asset Shares (fTokens)
/// @return _amountToReturn The amount of Asset Tokens to be transferred
function redeem(uint256 _shares, address _receiver, address _owner)
external
nonReentrant
returns (uint256 _amountToReturn)
{
if (_receiver == address(0)) revert InvalidReceiver();
// Check if withdraw is paused and revert if necessary
if (isWithdrawPaused) revert WithdrawPaused();
// Accrue interest if necessary
_addInterest();
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Calculate the number of assets to transfer based on the shares to burn
_amountToReturn = _totalAsset.toAmount(_shares, false);
// Execute the withdraw effects
_redeem(_totalAsset, _amountToReturn.toUint128(), _shares.toUint128(), _receiver, _owner, false);
}
/// @notice The ```previewWithdraw``` function returns the number of Asset Shares (fTokens) that would be burned for a given amount of Asset Tokens
/// @param _amount The amount of Asset Tokens to be withdrawn
/// @return _sharesToBurn The number of shares that would be burned
function previewWithdraw(uint256 _amount) external view returns (uint256 _sharesToBurn) {
(,,,, VaultAccount memory _totalAsset,) = previewAddInterest();
_sharesToBurn = _totalAsset.toShares(_amount, true);
}
/// @notice The ```withdraw``` function allows the caller to withdraw their Asset Tokens for a given amount of fTokens
/// @param _amount The amount to withdraw
/// @param _receiver The address to which the Asset Tokens will be transferred
/// @param _owner The owner of the Asset Shares (fTokens)
/// @return _sharesToBurn The number of shares (fTokens) that were burned
function withdraw(uint256 _amount, address _receiver, address _owner)
external
nonReentrant
returns (uint256 _sharesToBurn)
{
if (_receiver == address(0)) revert InvalidReceiver();
// Check if withdraw is paused and revert if necessary
if (isWithdrawPaused) revert WithdrawPaused();
// Accrue interest if necessary
_addInterest();
// Pull from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
// Calculate the number of shares to burn based on the amount to withdraw
_sharesToBurn = _totalAsset.toShares(_amount, true);
// Execute the withdraw effects
_redeem(_totalAsset, _amount.toUint128(), _sharesToBurn.toUint128(), _receiver, _owner, false);
}
// ============================================================================================
// Functions: Borrowing
// ============================================================================================
/// @notice The ```BorrowAsset``` event is emitted when a borrower increases their position
/// @param _borrower The borrower whose account was debited
/// @param _receiver The address to which the Asset Tokens were transferred
/// @param _borrowAmount The amount of Asset Tokens transferred
/// @param _sharesAdded The number of Borrow Shares the borrower was debited
event BorrowAsset(
address indexed _borrower, address indexed _receiver, uint256 _borrowAmount, uint256 _sharesAdded
);
/// @notice The ```_borrowAsset``` function is the internal implementation for borrowing assets
/// @param _borrowAmount The amount of the Asset Token to borrow
/// @param _receiver The address to receive the Asset Tokens
/// @return _sharesAdded The amount of borrow shares the msg.sender will be debited
function _borrowAsset(uint128 _borrowAmount, address _receiver) internal returns (uint256 _sharesAdded) {
// Get borrow accounting from storage to save gas
VaultAccount memory _totalBorrow = totalBorrow;
// Check available capital (not strictly necessary because balance will underflow, but better revert message)
uint256 _totalAssetsAvailable = _totalAssetAvailable(totalAsset, _totalBorrow, true);
if (_totalAssetsAvailable < _borrowAmount) {
revert InsufficientAssetsInContract(_totalAssetsAvailable, _borrowAmount);
}
uint256 _localAssetsAvailable = _totalAssetAvailable(totalAsset, _totalBorrow, false);
if (_localAssetsAvailable < _borrowAmount) {
uint256 _externalAmt = _borrowAmount - _localAssetsAvailable;
_depositFromVault(_externalAmt);
}
// Calculate the number of shares to add based on the amount to borrow
_sharesAdded = _totalBorrow.toShares(_borrowAmount, true);
// Effects: Bookkeeping to add shares & amounts to total Borrow accounting
_totalBorrow.amount += _borrowAmount;
_totalBorrow.shares += _sharesAdded.toUint128();
// NOTE: we can safely cast here because shares are always less than amount and _borrowAmount is uint128
// Effects: write back to storage
totalBorrow = _totalBorrow;
userBorrowShares[msg.sender] += _sharesAdded;
// Interactions
if (_receiver != address(this)) {
assetContract.safeTransfer(_receiver, _borrowAmount);
}
emit BorrowAsset(msg.sender, _receiver, _borrowAmount, _sharesAdded);
}
/// @notice The ```borrowAsset``` function allows a user to open/increase a borrow position
/// @dev Borrower must call ```ERC20.approve``` on the Collateral Token contract if applicable
/// @param _borrowAmount The amount of Asset Token to borrow
/// @param _collateralAmount The amount of Collateral Token to transfer to Pair
/// @param _receiver The address which will receive the Asset Tokens
/// @return _shares The number of borrow Shares the msg.sender will be debited
function borrowAsset(uint256 _borrowAmount, uint256 _collateralAmount, address _receiver)
external
nonReentrant
isSolvent(msg.sender)
returns (uint256 _shares)
{
if (_receiver == address(0)) revert InvalidReceiver();
// Accrue interest if necessary
_addInterest();
// Check if borrow will violate the borrow limit and revert if necessary
if (borrowLimit < totalBorrow.amount + _borrowAmount) revert ExceedsBorrowLimit();
// Update _exchangeRate and check if borrow is allowed based on deviation
(bool _isBorrowAllowed,,) = _updateExchangeRate();
if (!_isBorrowAllowed) revert ExceedsMaxOracleDeviation();
// Only add collateral if necessary
if (_collateralAmount > 0) {
_addCollateral(msg.sender, _collateralAmount, msg.sender);
}
// Effects: Call internal borrow function
_shares = _borrowAsset(_borrowAmount.toUint128(), _receiver);
}
/// @notice The ```AddCollateral``` event is emitted when a borrower adds collateral to their position
/// @param sender The source of funds for the new collateral
/// @param borrower The borrower account for which the collateral should be credited
/// @param collateralAmount The amount of Collateral Token to be transferred
event AddCollateral(address indexed sender, address indexed borrower, uint256 collateralAmount);
/// @notice The ```_addCollateral``` function is an internal implementation for adding collateral to a borrowers position
/// @param _sender The source of funds for the new collateral
/// @param _collateralAmount The amount of Collateral Token to be transferred
/// @param _borrower The borrower account for which the collateral should be credited
function _addCollateral(address _sender, uint256 _collateralAmount, address _borrower) internal {
// Effects: write to state
userCollateralBalance[_borrower] += _collateralAmount;
totalCollateral += _collateralAmount;
// Interactions
if (_sender != address(this)) {
collateralContract.safeTransferFrom(_sender, address(this), _collateralAmount);
}
emit AddCollateral(_sender, _borrower, _collateralAmount);
}
/// @notice The ```addCollateral``` function allows the caller to add Collateral Token to a borrowers position
/// @dev msg.sender must call ERC20.approve() on the Collateral Token contract prior to invocation
/// @param _collateralAmount The amount of Collateral Token to be added to borrower's position
/// @param _borrower The account to be credited
function addCollateral(uint256 _collateralAmount, address _borrower) external nonReentrant {
if (_borrower == address(0)) revert InvalidReceiver();
_addInterest();
_addCollateral(msg.sender, _collateralAmount, _borrower);
}
/// @notice The ```RemoveCollateral``` event is emitted when collateral is removed from a borrower's position
/// @param _sender The account from which funds are transferred
/// @param _collateralAmount The amount of Collateral Token to be transferred
/// @param _receiver The address to which Collateral Tokens will be transferred
event RemoveCollateral(
address indexed _sender, uint256 _collateralAmount, address indexed _receiver, address indexed _borrower
);
/// @notice The ```_removeCollateral``` function is the internal implementation for removing collateral from a borrower's position
/// @param _collateralAmount The amount of Collateral Token to remove from the borrower's position
/// @param _receiver The address to receive the Collateral Token transferred
/// @param _borrower The borrower whose account will be debited the Collateral amount
function _removeCollateral(uint256 _collateralAmount, address _receiver, address _borrower) internal {
// Effects: write to state
// NOTE: Following line will revert on underflow if _collateralAmount > userCollateralBalance
userCollateralBalance[_borrower] -= _collateralAmount;
// NOTE: Following line will revert on underflow if totalCollateral < _collateralAmount
totalCollateral -= _collateralAmount;
// Interactions
if (_receiver != address(this)) {
collateralContract.safeTransfer(_receiver, _collateralAmount);
}
emit RemoveCollateral(msg.sender, _collateralAmount, _receiver, _borrower);
}
/// @notice The ```removeCollateral``` function is used to remove collateral from msg.sender's borrow position
/// @dev msg.sender must be solvent after invocation or transaction will revert
/// @param _collateralAmount The amount of Collateral Token to transfer
/// @param _receiver The address to receive the transferred funds
function removeCollateral(uint256 _collateralAmount, address _receiver)
external
nonReentrant
isSolvent(msg.sender)
{
if (_receiver == address(0)) revert InvalidReceiver();
_addInterest();