-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathSiloSolvencyLib.sol
More file actions
173 lines (149 loc) · 7.77 KB
/
SiloSolvencyLib.sol
File metadata and controls
173 lines (149 loc) · 7.77 KB
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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;
import {MathUpgradeable} from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol";
import {ISiloOracle} from "../interfaces/ISiloOracle.sol";
import {ISiloLiquidation} from "../interfaces/ISiloLiquidation.sol";
import {SiloStdLib, ISiloConfig, IShareToken, ISilo} from "./SiloStdLib.sol";
import {SiloERC4626Lib} from "./SiloERC4626Lib.sol";
import {SiloLiquidationLib} from "./SiloLiquidationLib.sol";
import {SiloMathLib} from "./SiloMathLib.sol";
library SiloSolvencyLib {
struct LtvData {
ISiloOracle collateralOracle; // TODO remove oracles once we have deposit gas
ISiloOracle debtOracle;
uint256 borrowerProtectedAssets;
uint256 borrowerCollateralAssets;
uint256 borrowerDebtAssets;
}
uint256 internal constant _PRECISION_DECIMALS = 1e18;
uint256 internal constant _BASIS_POINTS = 1e4;
uint256 internal constant _INFINITY = type(uint256).max;
/// @dev check if config was given in correct order
/// @return orderCorrect TRUE means that order is correct OR `_borrower` has no debt and we can not really tell
function validConfigOrder(
address _collateralConfigDebtShareToken,
address _debtConfigDebtShareToken,
address _borrower
) external view returns (bool orderCorrect) {
uint256 debtShareTokenBalance = IShareToken(_debtConfigDebtShareToken).balanceOf(_borrower);
return
debtShareTokenBalance == 0 ? IShareToken(_collateralConfigDebtShareToken).balanceOf(_borrower) == 0 : true;
}
function isSolvent(
ISiloConfig.ConfigData memory _collateralConfig,
ISiloConfig.ConfigData memory _debtConfig,
address _borrower,
ISilo.AccrueInterestInMemory _accrueInMemory
) external view returns (bool) {
uint256 ltv = getLtv(_collateralConfig, _debtConfig, _borrower, ISilo.OracleType.Solvency, _accrueInMemory);
return ltv <= _collateralConfig.lt;
}
function isBelowMaxLtv(
ISiloConfig.ConfigData memory _collateralConfig,
ISiloConfig.ConfigData memory _debtConfig,
address _borrower,
ISilo.AccrueInterestInMemory _accrueInMemory
) external view returns (bool) {
uint256 ltv = getLtv(_collateralConfig, _debtConfig, _borrower, ISilo.OracleType.MaxLtv, _accrueInMemory);
return ltv <= _collateralConfig.maxLtv;
}
/// @dev calculation never reverts, if there is revert, then it is because of oracle
function calculateLtv(SiloSolvencyLib.LtvData memory _ltvData, address _collateralToken, address _debtToken)
internal
view
returns (uint256 sumOfBorrowerCollateralValue, uint256 totalBorrowerDebtValue, uint256 ltvInBp)
{
(
sumOfBorrowerCollateralValue, totalBorrowerDebtValue
) = getPositionValues(_ltvData, _collateralToken, _debtToken);
if (sumOfBorrowerCollateralValue == 0 && totalBorrowerDebtValue == 0) {
return (0, 0, 0);
} else if (sumOfBorrowerCollateralValue == 0) {
ltvInBp = _INFINITY;
} else {
// TODO when 128 the whole below math can be unchecked, cast to 256!
ltvInBp = totalBorrowerDebtValue * _BASIS_POINTS / sumOfBorrowerCollateralValue;
}
}
// solhint-disable-next-line function-max-lines
function getAssetsDataForLtvCalculations(
ISiloConfig.ConfigData memory _collateralConfig,
ISiloConfig.ConfigData memory _debtConfig,
address _borrower,
ISilo.OracleType _oracleType,
ISilo.AccrueInterestInMemory _accrueInMemory
) internal view returns (LtvData memory ltvData) {
// When calculating maxLtv, use maxLtv oracle. If maxLtv oracle is not set, fallback to solvency oracle
ltvData.debtOracle = _oracleType == ISilo.OracleType.MaxLtv && _debtConfig.maxLtvOracle != address(0)
? ISiloOracle(_debtConfig.maxLtvOracle)
: ISiloOracle(_debtConfig.solvencyOracle);
ltvData.collateralOracle = _oracleType == ISilo.OracleType.MaxLtv
&& _collateralConfig.maxLtvOracle != address(0)
? ISiloOracle(_collateralConfig.maxLtvOracle)
: ISiloOracle(_collateralConfig.solvencyOracle);
uint256 totalAssets;
uint256 totalShares;
uint256 shares;
(shares, totalShares) = SiloStdLib.getSharesAndTotalSupply(_collateralConfig.protectedShareToken, _borrower);
totalAssets = ISilo(_collateralConfig.silo).getProtectedAssets();
ltvData.borrowerProtectedAssets = SiloMathLib.convertToAssets(
shares, totalAssets, totalShares, MathUpgradeable.Rounding.Down, ISilo.AssetType.Protected
);
(shares, totalShares) = SiloStdLib.getSharesAndTotalSupply(_collateralConfig.collateralShareToken, _borrower);
totalAssets = _accrueInMemory == ISilo.AccrueInterestInMemory.Yes
? SiloStdLib.getTotalCollateralAssetsWithInterest(
_collateralConfig.silo,
_collateralConfig.interestRateModel,
_collateralConfig.daoFeeInBp,
_collateralConfig.deployerFeeInBp
)
: ISilo(_collateralConfig.silo).getCollateralAssets();
ltvData.borrowerCollateralAssets = SiloMathLib.convertToAssets(
shares, totalAssets, totalShares, MathUpgradeable.Rounding.Down, ISilo.AssetType.Collateral
);
(shares, totalShares) = SiloStdLib.getSharesAndTotalSupply(_debtConfig.debtShareToken, _borrower);
totalAssets = _accrueInMemory == ISilo.AccrueInterestInMemory.Yes
? SiloStdLib.getTotalDebtAssetsWithInterest(_debtConfig.silo, _debtConfig.interestRateModel)
: ISilo(_debtConfig.silo).getDebtAssets();
ltvData.borrowerDebtAssets = SiloMathLib.convertToAssets(
shares, totalAssets, totalShares, MathUpgradeable.Rounding.Up, ISilo.AssetType.Debt
);
}
function getPositionValues(LtvData memory _ltvData, address _collateralAsset, address _debtAsset)
internal
view
returns (uint256 sumOfCollateralValue, uint256 debtValue)
{
uint256 sumOfCollateralAssets;
// safe because we adding same token, so it is under same total supply
unchecked { sumOfCollateralAssets = _ltvData.borrowerProtectedAssets + _ltvData.borrowerCollateralAssets; }
if (sumOfCollateralAssets != 0) {
// if no oracle is set, assume price 1
sumOfCollateralValue = address(_ltvData.collateralOracle) != address(0)
? _ltvData.collateralOracle.quote(sumOfCollateralAssets, _collateralAsset)
: sumOfCollateralAssets;
}
if (_ltvData.borrowerDebtAssets != 0) {
// if no oracle is set, assume price 1
debtValue = address(_ltvData.debtOracle) != address(0)
? _ltvData.debtOracle.quote(_ltvData.borrowerDebtAssets, _debtAsset)
: _ltvData.borrowerDebtAssets;
}
}
/// @dev Calculates LTV for user. It is used in core logic. Non-view function is needed in case the oracle
/// has to write some data to storage to protect ie. from read re-entracy like in curve pools.
/// @return ltv Loan-to-Value
function getLtv(
ISiloConfig.ConfigData memory _collateralConfig,
ISiloConfig.ConfigData memory _debtConfig,
address _borrower,
ISilo.OracleType _oracleType,
ISilo.AccrueInterestInMemory _accrueInMemory
) internal view returns (uint256 ltv) {
// TODO: early return if no debt
LtvData memory ltvData =
getAssetsDataForLtvCalculations(_collateralConfig, _debtConfig, _borrower, _oracleType, _accrueInMemory);
if (ltvData.borrowerDebtAssets == 0) return 0;
(,, ltv) = calculateLtv(ltvData, _collateralConfig.token, _debtConfig.token);
}
}