-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathSiloLiquidationLens.sol
More file actions
232 lines (199 loc) · 8.83 KB
/
SiloLiquidationLens.sol
File metadata and controls
232 lines (199 loc) · 8.83 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
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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "../interfaces/ISilo.sol";
import "../lib/SolvencyV2.sol";
import "../lib/EasyMath.sol";
import "../lib/Ping.sol";
/// @title SiloLiquidationLens
/// @custom:security-contact security@silo.finance
contract SiloLiquidationLens {
using EasyMath for uint256;
ISiloRepository immutable public siloRepository;
error InvalidSiloRepository();
constructor (ISiloRepository _siloRepo) {
if (!Ping.pong(_siloRepo.siloRepositoryPing)) revert InvalidSiloRepository();
siloRepository = _siloRepo;
}
/// @dev view method of ISilo.flashLiquidate
/// @param _silo Silo address from which to read data
/// @param _users array of users for witch we want to get liquidation data
/// @return assets array of all processed assets (collateral + debt, including removed)
/// @return receivedCollaterals receivedCollaterals[userId][assetId] => amount
/// amounts of collaterals send to `_flashReceiver`
/// @return shareAmountsToRepay shareAmountsToRepaid[userId][assetId] => amount
/// required amounts of debt to be repaid
function flashLiquidateView(ISilo _silo, address[] memory _users)
external
view
returns (
address[] memory assets,
uint256[][] memory receivedCollaterals,
uint256[][] memory shareAmountsToRepay
)
{
return flashLiquidateView(_silo, _users, 0);
}
/// @dev view method of ISilo.flashLiquidate
/// @param _silo Silo address from which to read data
/// @param _users array of users for witch we want to get liquidation data
/// @param _timestampOffset offset to reduce current timestamp while doing calculation for interest
/// @return assets array of all processed assets (collateral + debt, including removed)
/// @return receivedCollaterals receivedCollaterals[userId][assetId] => amount
/// amounts of collaterals send to `_flashReceiver`
/// @return shareAmountsToRepay shareAmountsToRepaid[userId][assetId] => amount
/// required amounts of debt to be repaid
function flashLiquidateView(ISilo _silo, address[] memory _users, uint64 _timestampOffset)
public
view
returns (
address[] memory assets,
uint256[][] memory receivedCollaterals,
uint256[][] memory shareAmountsToRepay
)
{
assets = _silo.getAssets();
uint256 usersLength = _users.length;
receivedCollaterals = new uint256[][](usersLength);
shareAmountsToRepay = new uint256[][](usersLength);
for (uint256 i = 0; i < usersLength; i++) {
(
receivedCollaterals[i],
shareAmountsToRepay[i]
) = _userLiquidationView(_silo, assets, _users[i], _timestampOffset);
}
}
/// @dev gets interest rates model object
/// @param _silo Silo address from which to read data
/// @param _asset asset for which to calculate interest rate
/// @return IInterestRateModel interest rates model object
function getModel(ISilo _silo, address _asset) public view returns (IInterestRateModel) {
return IInterestRateModel(siloRepository.getInterestRateModel(address(_silo), _asset));
}
function _userLiquidationView(ISilo _silo, address[] memory _assets, address _user, uint64 _timestampOffset)
internal
view
returns (uint256[] memory receivedCollaterals, uint256[] memory shareAmountsToRepay)
{
// gracefully fail if _user is solvent
if (_silo.isSolvent(_user)) {
uint256[] memory empty = new uint256[](_assets.length);
return (empty, empty);
}
(receivedCollaterals, shareAmountsToRepay) = _flashUserLiquidationView(_silo, _assets, _user, _timestampOffset);
}
function _flashUserLiquidationView(
ISilo _silo,
address[] memory _allSiloAssets,
address _borrower,
uint64 _timestampOffset
)
internal
view
returns (uint256[] memory receivedCollaterals, uint256[] memory amountsToRepay)
{
uint256 assetsLength = _allSiloAssets.length;
receivedCollaterals = new uint256[](assetsLength);
amountsToRepay = new uint256[](assetsLength);
uint256 protocolLiquidationFee = siloRepository.protocolLiquidationFee();
uint64 blockTimestamp = uint64(block.timestamp) - _timestampOffset;
for (uint256 i = 0; i < assetsLength; i++) {
ISilo.AssetStorage memory _state = _silo.assetStorage(_allSiloAssets[i]);
ISilo.AssetInterestData memory _assetInterestData = _silo.interestData(_allSiloAssets[i]);
_accrueInterestView(_silo, _state, _assetInterestData, _allSiloAssets[i], blockTimestamp);
// we do not allow for partial repayment on liquidation, that's why max
(amountsToRepay[i],) = _calculateDebtAmountAndShare(_state, _borrower);
uint256 withdrawnOnlyAmount = _calculateWithdrawAssetAmount(
_state.collateralOnlyDeposits,
_state.collateralOnlyToken,
_borrower,
protocolLiquidationFee,
_assetInterestData.protocolFees
);
uint256 withdrawnAmount = _calculateWithdrawAssetAmount(
_state.totalDeposits,
_state.collateralToken,
_borrower,
protocolLiquidationFee,
_assetInterestData.protocolFees
);
receivedCollaterals[i] = withdrawnOnlyAmount + withdrawnAmount;
}
}
function _accrueInterestView(
ISilo _silo,
ISilo.AssetStorage memory _state,
ISilo.AssetInterestData memory _assetInterestData,
address _asset,
uint64 _blockTimestamp
)
internal
view
{
uint256 lastTimestamp = _assetInterestData.interestRateTimestamp;
// This is the first time, so we can return early and save some gas
if (lastTimestamp == 0) {
_assetInterestData.interestRateTimestamp = _blockTimestamp;
return;
}
// Interest has already been accrued this block
if (lastTimestamp == _blockTimestamp) {
return;
}
uint256 rcomp = getModel(_silo, _asset).getCompoundInterestRate(address(_silo), _asset, _blockTimestamp);
uint256 protocolShareFee = siloRepository.protocolShareFee();
uint256 totalBorrowAmountCached = _state.totalBorrowAmount;
uint256 totalInterest = totalBorrowAmountCached * rcomp / SolvencyV2._PRECISION_DECIMALS;
uint256 protocolShare = totalInterest * protocolShareFee / SolvencyV2._PRECISION_DECIMALS;
uint256 depositorsShare = totalInterest - protocolShare;
// update contract state
_state.totalBorrowAmount = totalBorrowAmountCached + totalInterest;
_state.totalDeposits = _state.totalDeposits + depositorsShare;
_assetInterestData.protocolFees = _assetInterestData.protocolFees + protocolShare;
_assetInterestData.interestRateTimestamp = _blockTimestamp;
}
function _calculateDebtAmountAndShare(ISilo.AssetStorage memory _assetStorage, address _borrower)
internal
view
returns (uint256 amount, uint256 repayShare)
{
repayShare = _assetStorage.debtToken.balanceOf(_borrower);
uint256 debtTokenTotalSupply = _assetStorage.debtToken.totalSupply();
uint256 totalBorrowed = _assetStorage.totalBorrowAmount;
amount = repayShare.toAmountRoundUp(totalBorrowed, debtTokenTotalSupply);
}
function _calculateWithdrawAssetAmount(
uint256 _assetTotalDeposits,
IShareToken _shareToken,
address _depositor,
uint256 _protocolLiquidationFee,
uint256 _protocolFees
)
internal
view
returns (uint256 withdrawnAmount)
{
uint256 burnedShare = _shareToken.balanceOf(_depositor);
withdrawnAmount = burnedShare.toAmount(_assetTotalDeposits, _shareToken.totalSupply());
if (withdrawnAmount == 0) return 0;
if (_protocolLiquidationFee != 0) {
withdrawnAmount = _applyLiquidationFee(withdrawnAmount, _protocolLiquidationFee, _protocolFees);
}
}
function _applyLiquidationFee(
uint256 _amount,
uint256 _protocolLiquidationFee,
uint256 _protocolFees
)
internal
pure
returns (uint256 change)
{
uint256 liquidationFeeAmount;
(liquidationFeeAmount,) = SolvencyV2.calculateLiquidationFee(_protocolFees, _amount, _protocolLiquidationFee);
unchecked {
// if fees will not be higher than 100% this will not underflow, this is responsibility of siloRepository
// in case we do underflow, we can expect liquidator reject tx because of too little change
change = _amount - liquidationFeeAmount;
}
}
}