@gearbox-protocol/periphery-v3 1.7.0-next.1 → 1.7.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/compressors/CreditAccountCompressor.sol +443 -0
- package/contracts/compressors/PriceFeedCompressor.sol +225 -0
- package/contracts/compressors/Types.sol +118 -0
- package/contracts/interfaces/IStateSerializer.sol +10 -0
- package/contracts/interfaces/IStateSerializerTrait.sol +10 -0
- package/contracts/libraries/NestedPriceFeeds.sol +153 -0
- package/contracts/serializers/oracles/BPTWeightedPriceFeedSerializer.sol +26 -0
- package/contracts/serializers/oracles/BoundedPriceFeedSerializer.sol +15 -0
- package/contracts/serializers/oracles/LPPriceFeedSerializer.sol +46 -0
- package/contracts/serializers/oracles/PythPriceFeedSerializer.sol +15 -0
- package/contracts/serializers/oracles/RedstonePriceFeedSerializer.sol +35 -0
- package/package.json +6 -3
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
+
|
|
8
|
+
import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol";
|
|
9
|
+
import {ICreditAccountV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditAccountV3.sol";
|
|
10
|
+
import {
|
|
11
|
+
CollateralCalcTask,
|
|
12
|
+
CollateralDebtData,
|
|
13
|
+
ICreditManagerV3
|
|
14
|
+
} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
|
|
15
|
+
import {IPoolQuotaKeeperV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolQuotaKeeperV3.sol";
|
|
16
|
+
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
|
|
17
|
+
import {SanityCheckTrait} from "@gearbox-protocol/core-v3/contracts/traits/SanityCheckTrait.sol";
|
|
18
|
+
|
|
19
|
+
import {IAddressProviderV3} from "@gearbox-protocol/governance/contracts/interfaces/IAddressProviderV3.sol";
|
|
20
|
+
import {IMarketConfiguratorV3} from "@gearbox-protocol/governance/contracts/interfaces/IMarketConfiguratorV3.sol";
|
|
21
|
+
|
|
22
|
+
import {CreditAccountData, CreditAccountFilter, CreditManagerFilter, TokenInfo} from "./Types.sol";
|
|
23
|
+
|
|
24
|
+
/// @title Credit account compressor
|
|
25
|
+
/// @notice Allows to fetch data on all credit accounts matching certain criteria in an efficient manner
|
|
26
|
+
/// @dev The contract is not gas optimized and is thus not recommended for on-chain use
|
|
27
|
+
/// @dev The querying functions exist in two modes, with one of them trying to YOLO-load everything and the other
|
|
28
|
+
/// one loading as many accounts as fit in the call gas limit to implement the tightest pagination possible
|
|
29
|
+
contract CreditAccountCompressor is IVersion, SanityCheckTrait {
|
|
30
|
+
/// @notice Contract version
|
|
31
|
+
uint256 public constant override version = 3_10;
|
|
32
|
+
|
|
33
|
+
/// @notice Address provider contract address
|
|
34
|
+
address public immutable addressProvider;
|
|
35
|
+
|
|
36
|
+
/// @dev Maximum number of accounts to consider in a single step to avoid excessive memory expansion
|
|
37
|
+
uint256 internal constant STEP_SIZE = 1000;
|
|
38
|
+
|
|
39
|
+
/// @notice Thrown when address provider is not a contract or does not implement `marketConfigurators()`
|
|
40
|
+
error InvalidAddressProviderException();
|
|
41
|
+
|
|
42
|
+
/// @notice Constructor
|
|
43
|
+
/// @param addressProvider_ Address provider contract address
|
|
44
|
+
constructor(address addressProvider_) nonZeroAddress(addressProvider_) {
|
|
45
|
+
if (addressProvider_.code.length == 0) revert InvalidAddressProviderException();
|
|
46
|
+
try IAddressProviderV3(addressProvider_).marketConfigurators() {}
|
|
47
|
+
catch {
|
|
48
|
+
revert InvalidAddressProviderException();
|
|
49
|
+
}
|
|
50
|
+
addressProvider = addressProvider_;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// -------- //
|
|
54
|
+
// QUERYING //
|
|
55
|
+
// -------- //
|
|
56
|
+
|
|
57
|
+
/// @notice Returns data for a particular `creditAccount`
|
|
58
|
+
function getCreditAccountData(address creditAccount) external view returns (CreditAccountData memory) {
|
|
59
|
+
address creditManager = ICreditAccountV3(creditAccount).creditManager();
|
|
60
|
+
return _getCreditAccountData(creditAccount, creditManager);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// @notice Returns data for credit accounts that match `caFilter` in credit managers matching `cmFilter`
|
|
64
|
+
/// @dev The `offset` parameter specifies the position to start scanning from in the list of all
|
|
65
|
+
/// credit accounts opened in credit managers matching `cmFilter`
|
|
66
|
+
/// @dev The `false` value of `finished` return variable indicates that gas supplied with a call
|
|
67
|
+
/// was insufficient to process all the accounts and next iteration is needed which should
|
|
68
|
+
/// start from `nextOffset` position
|
|
69
|
+
function getCreditAccounts(CreditManagerFilter memory cmFilter, CreditAccountFilter memory caFilter, uint256 offset)
|
|
70
|
+
external
|
|
71
|
+
view
|
|
72
|
+
returns (CreditAccountData[] memory data, bool finished, uint256 nextOffset)
|
|
73
|
+
{
|
|
74
|
+
uint256 num = countCreditAccounts(cmFilter, caFilter, offset);
|
|
75
|
+
nextOffset = offset;
|
|
76
|
+
|
|
77
|
+
// FIXME: If the number of accounts matching the filter is much larger than the number of accounts that
|
|
78
|
+
// can realistically be processed with most providers, pre-allocation burns unnecessarily large amount
|
|
79
|
+
// of gas due to quadratic memory expansion cost.
|
|
80
|
+
// An idea for the mitigation is to calculate the maximum memory pointer that can possibly be reached
|
|
81
|
+
// during calculations, and write resulting data after it, only expanding memory when actually needed.
|
|
82
|
+
data = new CreditAccountData[](num);
|
|
83
|
+
uint256 dataOffset;
|
|
84
|
+
|
|
85
|
+
// FIXME: This should be proven to be enough to properly return the data, and possibly made tighter
|
|
86
|
+
uint256 gasThreshold = gasleft() / 64;
|
|
87
|
+
|
|
88
|
+
address[] memory creditManagers = _getCreditManagers(cmFilter);
|
|
89
|
+
for (uint256 i; i < creditManagers.length; ++i) {
|
|
90
|
+
uint256 len = ICreditManagerV3(creditManagers[i]).creditAccountsLen();
|
|
91
|
+
|
|
92
|
+
// first, we need to get to the `offset` position
|
|
93
|
+
if (len <= offset) {
|
|
94
|
+
offset -= len;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
while (offset < len) {
|
|
99
|
+
bool outOfGas;
|
|
100
|
+
uint256 processed;
|
|
101
|
+
|
|
102
|
+
// circumvent the "Stack too deep." error
|
|
103
|
+
uint256 offset_ = offset;
|
|
104
|
+
|
|
105
|
+
(dataOffset, outOfGas, processed) = _getCreditAccounts({
|
|
106
|
+
creditManager: creditManagers[i],
|
|
107
|
+
filter: caFilter,
|
|
108
|
+
data: data,
|
|
109
|
+
dataOffset: dataOffset,
|
|
110
|
+
offset: offset_,
|
|
111
|
+
limit: STEP_SIZE,
|
|
112
|
+
gasThreshold: gasThreshold
|
|
113
|
+
});
|
|
114
|
+
offset += processed;
|
|
115
|
+
nextOffset += processed;
|
|
116
|
+
|
|
117
|
+
if (outOfGas) {
|
|
118
|
+
// trim the array to its actual size
|
|
119
|
+
assembly {
|
|
120
|
+
mstore(data, dataOffset)
|
|
121
|
+
}
|
|
122
|
+
return (data, dataOffset == num, nextOffset);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
offset = 0;
|
|
126
|
+
}
|
|
127
|
+
return (data, true, nextOffset);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// @dev Same as above but with no gas limit control
|
|
131
|
+
function getCreditAccounts(CreditManagerFilter memory cmFilter, CreditAccountFilter memory caFilter)
|
|
132
|
+
external
|
|
133
|
+
view
|
|
134
|
+
returns (CreditAccountData[] memory data)
|
|
135
|
+
{
|
|
136
|
+
uint256 num = countCreditAccounts(cmFilter, caFilter, 0);
|
|
137
|
+
data = new CreditAccountData[](num);
|
|
138
|
+
uint256 dataOffset;
|
|
139
|
+
|
|
140
|
+
address[] memory creditManagers = _getCreditManagers(cmFilter);
|
|
141
|
+
for (uint256 i; i < creditManagers.length; ++i) {
|
|
142
|
+
uint256 len = ICreditManagerV3(creditManagers[i]).creditAccountsLen();
|
|
143
|
+
(dataOffset,,) = _getCreditAccounts({
|
|
144
|
+
creditManager: creditManagers[i],
|
|
145
|
+
filter: caFilter,
|
|
146
|
+
data: data,
|
|
147
|
+
dataOffset: dataOffset,
|
|
148
|
+
offset: 0,
|
|
149
|
+
limit: len,
|
|
150
|
+
gasThreshold: 0
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// @notice Returns data for credit accounts that match `caFilter` in a given `creditManager`
|
|
156
|
+
/// @dev The `offset` parameter specifies the position to start scanning from in the list of all
|
|
157
|
+
/// credit accounts opened in `creditManager`
|
|
158
|
+
/// @dev The `false` value of `finished` return variable indicates that gas supplied with a call
|
|
159
|
+
/// was insufficient to process all the accounts and next iteration is needed which should
|
|
160
|
+
/// start from `nextOffset` position
|
|
161
|
+
function getCreditAccounts(address creditManager, CreditAccountFilter memory caFilter, uint256 offset)
|
|
162
|
+
external
|
|
163
|
+
view
|
|
164
|
+
returns (CreditAccountData[] memory data, bool finished, uint256 nextOffset)
|
|
165
|
+
{
|
|
166
|
+
uint256 num = countCreditAccounts(creditManager, caFilter, offset);
|
|
167
|
+
nextOffset = offset;
|
|
168
|
+
|
|
169
|
+
// FIXME: If the number of accounts matching the filter is much larger than the number of accounts that
|
|
170
|
+
// can realistically be processed with most providers, pre-allocation burns unnecessarily large amount
|
|
171
|
+
// of gas due to quadratic memory expansion cost.
|
|
172
|
+
// An idea for the mitigation is to calculate the maximum memory pointer that can possibly be reached
|
|
173
|
+
// during calculations, and write resulting data after it, only expanding memory when actually needed.
|
|
174
|
+
data = new CreditAccountData[](num);
|
|
175
|
+
uint256 dataOffset;
|
|
176
|
+
|
|
177
|
+
// FIXME: This should be proven to be enough to properly return the data, and possibly made tighter
|
|
178
|
+
uint256 gasThreshold = gasleft() / 64;
|
|
179
|
+
|
|
180
|
+
uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
|
|
181
|
+
while (offset < len) {
|
|
182
|
+
bool outOfGas;
|
|
183
|
+
uint256 processed;
|
|
184
|
+
(dataOffset, outOfGas, processed) = _getCreditAccounts({
|
|
185
|
+
creditManager: creditManager,
|
|
186
|
+
filter: caFilter,
|
|
187
|
+
data: data,
|
|
188
|
+
dataOffset: dataOffset,
|
|
189
|
+
offset: offset,
|
|
190
|
+
limit: STEP_SIZE,
|
|
191
|
+
gasThreshold: gasThreshold
|
|
192
|
+
});
|
|
193
|
+
offset += processed;
|
|
194
|
+
nextOffset += processed;
|
|
195
|
+
|
|
196
|
+
if (outOfGas) {
|
|
197
|
+
// trim the array to its actual size
|
|
198
|
+
assembly {
|
|
199
|
+
mstore(data, dataOffset)
|
|
200
|
+
}
|
|
201
|
+
return (data, dataOffset == num, nextOffset);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return (data, true, nextOffset);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// @dev Same as above but with no gas limit control
|
|
208
|
+
function getCreditAccounts(address creditManager, CreditAccountFilter memory caFilter)
|
|
209
|
+
external
|
|
210
|
+
view
|
|
211
|
+
returns (CreditAccountData[] memory data)
|
|
212
|
+
{
|
|
213
|
+
uint256 num = countCreditAccounts(creditManager, caFilter, 0);
|
|
214
|
+
data = new CreditAccountData[](num);
|
|
215
|
+
|
|
216
|
+
uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
|
|
217
|
+
_getCreditAccounts({
|
|
218
|
+
creditManager: creditManager,
|
|
219
|
+
filter: caFilter,
|
|
220
|
+
data: data,
|
|
221
|
+
dataOffset: 0,
|
|
222
|
+
offset: 0,
|
|
223
|
+
limit: len,
|
|
224
|
+
gasThreshold: 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// -------- //
|
|
229
|
+
// COUNTING //
|
|
230
|
+
// -------- //
|
|
231
|
+
|
|
232
|
+
/// @notice Counts credit accounts that match `caFilter` in credit managers matching `cmFilter`
|
|
233
|
+
/// @dev The `offset` parameter specifies the position to start scanning from in the list of all
|
|
234
|
+
/// credit accounts opened in credit managers matching `cmFilter`
|
|
235
|
+
function countCreditAccounts(
|
|
236
|
+
CreditManagerFilter memory cmFilter,
|
|
237
|
+
CreditAccountFilter memory caFilter,
|
|
238
|
+
uint256 offset
|
|
239
|
+
) public view returns (uint256 num) {
|
|
240
|
+
address[] memory creditManagers = _getCreditManagers(cmFilter);
|
|
241
|
+
|
|
242
|
+
for (uint256 i; i < creditManagers.length; ++i) {
|
|
243
|
+
uint256 len = ICreditManagerV3(creditManagers[i]).creditAccountsLen();
|
|
244
|
+
|
|
245
|
+
// first, we need to get to the `offset` position
|
|
246
|
+
if (len <= offset) {
|
|
247
|
+
offset -= len;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
while (offset < len) {
|
|
252
|
+
num += _countCreditAccounts(creditManagers[i], caFilter, offset, STEP_SIZE);
|
|
253
|
+
offset += STEP_SIZE;
|
|
254
|
+
}
|
|
255
|
+
offset = 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// @notice Counts credit accounts that match `caFilter` in a given `creditManager`
|
|
260
|
+
/// @dev The `offset` parameter specifies the position to start scanning from in the list of all
|
|
261
|
+
/// credit accounts opened in `creditManager`
|
|
262
|
+
function countCreditAccounts(address creditManager, CreditAccountFilter memory caFilter, uint256 offset)
|
|
263
|
+
public
|
|
264
|
+
view
|
|
265
|
+
returns (uint256 num)
|
|
266
|
+
{
|
|
267
|
+
uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
|
|
268
|
+
while (offset < len) {
|
|
269
|
+
num += _countCreditAccounts(creditManager, caFilter, offset, STEP_SIZE);
|
|
270
|
+
offset += STEP_SIZE;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// --------- //
|
|
275
|
+
// INTERNALS //
|
|
276
|
+
// --------- //
|
|
277
|
+
|
|
278
|
+
/// @dev Querying implementation
|
|
279
|
+
function _getCreditAccounts(
|
|
280
|
+
address creditManager,
|
|
281
|
+
CreditAccountFilter memory filter,
|
|
282
|
+
CreditAccountData[] memory data,
|
|
283
|
+
uint256 dataOffset,
|
|
284
|
+
uint256 offset,
|
|
285
|
+
uint256 limit,
|
|
286
|
+
uint256 gasThreshold
|
|
287
|
+
) internal view returns (uint256, bool, uint256) {
|
|
288
|
+
address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, limit);
|
|
289
|
+
uint256 len = creditAccounts.length;
|
|
290
|
+
for (uint256 i; i < len; ++i) {
|
|
291
|
+
// early return if remaining gas is below given threshold
|
|
292
|
+
if (gasleft() < gasThreshold) return (dataOffset, true, i);
|
|
293
|
+
if (_checkFilterMatch(creditAccounts[i], creditManager, filter)) {
|
|
294
|
+
data[dataOffset++] = _getCreditAccountData(creditAccounts[i], creditManager);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return (dataOffset, false, len);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// @dev Counting implementation
|
|
301
|
+
function _countCreditAccounts(
|
|
302
|
+
address creditManager,
|
|
303
|
+
CreditAccountFilter memory filter,
|
|
304
|
+
uint256 offset,
|
|
305
|
+
uint256 limit
|
|
306
|
+
) internal view returns (uint256 num) {
|
|
307
|
+
address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, limit);
|
|
308
|
+
for (uint256 i; i < creditAccounts.length; ++i) {
|
|
309
|
+
if (_checkFilterMatch(creditAccounts[i], creditManager, filter)) {
|
|
310
|
+
++num;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// @dev Data loading implementation
|
|
316
|
+
function _getCreditAccountData(address creditAccount, address creditManager)
|
|
317
|
+
internal
|
|
318
|
+
view
|
|
319
|
+
returns (CreditAccountData memory data)
|
|
320
|
+
{
|
|
321
|
+
data.creditAccount = creditAccount;
|
|
322
|
+
data.creditManager = creditManager;
|
|
323
|
+
data.creditFacade = ICreditManagerV3(creditManager).creditFacade();
|
|
324
|
+
data.underlying = ICreditManagerV3(creditManager).underlying();
|
|
325
|
+
data.owner = ICreditManagerV3(creditManager).getBorrowerOrRevert(creditAccount);
|
|
326
|
+
|
|
327
|
+
CollateralDebtData memory cdd =
|
|
328
|
+
ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_ONLY);
|
|
329
|
+
data.enabledTokensMask = cdd.enabledTokensMask;
|
|
330
|
+
data.debt = cdd.debt;
|
|
331
|
+
data.accruedInterest = cdd.accruedInterest;
|
|
332
|
+
data.accruedFees = cdd.accruedFees;
|
|
333
|
+
|
|
334
|
+
// collateral is computed separately since it might revert on `balanceOf` and `latestRoundData` calls
|
|
335
|
+
try ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL)
|
|
336
|
+
returns (CollateralDebtData memory cdd_) {
|
|
337
|
+
data.totalDebtUSD = cdd.totalDebtUSD;
|
|
338
|
+
data.totalValueUSD = cdd_.totalValueUSD;
|
|
339
|
+
data.twvUSD = cdd_.twvUSD;
|
|
340
|
+
data.totalValue = cdd.totalValue;
|
|
341
|
+
data.healthFactor = cdd.totalDebtUSD == 0 ? type(uint256).max : cdd.twvUSD * 1e18 / cdd.totalDebtUSD;
|
|
342
|
+
data.success = true;
|
|
343
|
+
} catch {}
|
|
344
|
+
|
|
345
|
+
uint256 numTokens = ICreditManagerV3(creditManager).collateralTokensCount();
|
|
346
|
+
data.tokens = new TokenInfo[](numTokens);
|
|
347
|
+
for (uint256 i; i < numTokens; ++i) {
|
|
348
|
+
address token = ICreditManagerV3(creditManager).getTokenByMask(1 << i);
|
|
349
|
+
data.tokens[i].token = token;
|
|
350
|
+
|
|
351
|
+
try IERC20(token).balanceOf(creditAccount) returns (uint256 balance) {
|
|
352
|
+
data.tokens[i].balance = balance;
|
|
353
|
+
data.tokens[i].success = true;
|
|
354
|
+
} catch {}
|
|
355
|
+
|
|
356
|
+
// down goes quotas logic which does not apply to underlying
|
|
357
|
+
if (i == 0) continue;
|
|
358
|
+
|
|
359
|
+
(data.tokens[i].quota,) =
|
|
360
|
+
IPoolQuotaKeeperV3(cdd._poolQuotaKeeper).getQuotaAndOutstandingInterest(creditAccount, token);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// @dev Credit account filtering implementation
|
|
365
|
+
function _checkFilterMatch(address creditAccount, address creditManager, CreditAccountFilter memory filter)
|
|
366
|
+
internal
|
|
367
|
+
view
|
|
368
|
+
returns (bool)
|
|
369
|
+
{
|
|
370
|
+
if (filter.owner != address(0)) {
|
|
371
|
+
address owner = ICreditManagerV3(creditManager).getBorrowerOrRevert(creditAccount);
|
|
372
|
+
if (owner != filter.owner) return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!filter.includeZeroDebt) {
|
|
376
|
+
(uint256 debt,,,,,,,) = ICreditManagerV3(creditManager).creditAccountInfo(creditAccount);
|
|
377
|
+
if (debt == 0) return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (filter.minHealthFactor != 0 || filter.maxHealthFactor != 0 || filter.reverting) {
|
|
381
|
+
try ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL)
|
|
382
|
+
returns (CollateralDebtData memory cdd) {
|
|
383
|
+
if (filter.reverting) return false;
|
|
384
|
+
uint256 healthFactor = cdd.totalDebtUSD == 0 ? type(uint256).max : cdd.twvUSD * 1e18 / cdd.totalDebtUSD;
|
|
385
|
+
if (filter.minHealthFactor != 0 && healthFactor < filter.minHealthFactor) return false;
|
|
386
|
+
if (filter.maxHealthFactor != 0 && healthFactor > filter.maxHealthFactor) return false;
|
|
387
|
+
} catch {
|
|
388
|
+
if (!filter.reverting) return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/// @dev Credit managers discovery
|
|
396
|
+
function _getCreditManagers(CreditManagerFilter memory filter)
|
|
397
|
+
internal
|
|
398
|
+
view
|
|
399
|
+
returns (address[] memory creditManagers)
|
|
400
|
+
{
|
|
401
|
+
address[] memory configurators = IAddressProviderV3(addressProvider).marketConfigurators();
|
|
402
|
+
|
|
403
|
+
// rough estimate of maximum number of credit managers
|
|
404
|
+
uint256 max;
|
|
405
|
+
for (uint256 i; i < configurators.length; ++i) {
|
|
406
|
+
address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
|
|
407
|
+
max += IContractsRegister(cr).getCreditManagers().length;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// allocate the array with maximum potentially needed size (total number of credit managers
|
|
411
|
+
// can be assumed to be relatively small, so memary expansion cost is not an issue here)
|
|
412
|
+
creditManagers = new address[](max);
|
|
413
|
+
uint256 num;
|
|
414
|
+
for (uint256 i; i < configurators.length; ++i) {
|
|
415
|
+
if (filter.curator != address(0)) {
|
|
416
|
+
if (IMarketConfiguratorV3(configurators[i]).owner() != filter.curator) continue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
|
|
420
|
+
address[] memory managers = IContractsRegister(cr).getCreditManagers();
|
|
421
|
+
for (uint256 j; j < managers.length; ++j) {
|
|
422
|
+
// we're only concerned with v3 contracts
|
|
423
|
+
uint256 ver = IVersion(managers[i]).version();
|
|
424
|
+
if (ver < 3_00 || ver > 3_99) continue;
|
|
425
|
+
|
|
426
|
+
if (filter.pool != address(0)) {
|
|
427
|
+
if (ICreditManagerV3(managers[i]).pool() != filter.pool) continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (filter.underlying != address(0)) {
|
|
431
|
+
if (ICreditManagerV3(managers[i]).underlying() != filter.underlying) continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
creditManagers[num++] = managers[j];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// trim the array to its actual size
|
|
439
|
+
assembly {
|
|
440
|
+
mstore(creditManagers, num)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
7
|
+
|
|
8
|
+
import {AddressIsNotContractException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";
|
|
9
|
+
import {IPriceOracleV3, PriceFeedParams} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol";
|
|
10
|
+
import {IPriceFeed, IUpdatablePriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol";
|
|
11
|
+
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
|
|
12
|
+
import {PriceFeedType} from "@gearbox-protocol/sdk-gov/contracts/PriceFeedType.sol";
|
|
13
|
+
|
|
14
|
+
import {IStateSerializer} from "../interfaces/IStateSerializer.sol";
|
|
15
|
+
import {IStateSerializerTrait} from "../interfaces/IStateSerializerTrait.sol";
|
|
16
|
+
import {NestedPriceFeeds} from "../libraries/NestedPriceFeeds.sol";
|
|
17
|
+
import {BoundedPriceFeedSerializer} from "../serializers/oracles/BoundedPriceFeedSerializer.sol";
|
|
18
|
+
import {BPTWeightedPriceFeedSerializer} from "../serializers/oracles/BPTWeightedPriceFeedSerializer.sol";
|
|
19
|
+
import {LPPriceFeedSerializer} from "../serializers/oracles/LPPriceFeedSerializer.sol";
|
|
20
|
+
import {PythPriceFeedSerializer} from "../serializers/oracles/PythPriceFeedSerializer.sol";
|
|
21
|
+
import {RedstonePriceFeedSerializer} from "../serializers/oracles/RedstonePriceFeedSerializer.sol";
|
|
22
|
+
import {PriceFeedAnswer, PriceFeedMapEntry, PriceFeedTreeNode} from "./Types.sol";
|
|
23
|
+
|
|
24
|
+
interface ImplementsPriceFeedType {
|
|
25
|
+
/// @dev Annotates `priceFeedType` as `uint8` instead of `PriceFeedType` enum to support future types
|
|
26
|
+
function priceFeedType() external view returns (uint8);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @dev Price oracle with version below `3_10` has some important interface differences:
|
|
30
|
+
/// - it does not implement `getTokens`
|
|
31
|
+
/// - it only allows to fetch staleness period of a currently active price feed
|
|
32
|
+
interface IPriceOracleV3Legacy {
|
|
33
|
+
/// @dev Older signature for fetching main and reserve feeds
|
|
34
|
+
function priceFeedsRaw(address token, bool reserve) external view returns (address);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// @title Price feed compressor
|
|
38
|
+
/// @notice Allows to fetch all useful data from price oracle in a single call
|
|
39
|
+
/// @dev The contract is not gas optimized and is thus not recommended for on-chain use
|
|
40
|
+
contract PriceFeedCompressor is IVersion, Ownable {
|
|
41
|
+
using NestedPriceFeeds for IPriceFeed;
|
|
42
|
+
|
|
43
|
+
/// @notice Contract version
|
|
44
|
+
uint256 public constant override version = 3_10;
|
|
45
|
+
|
|
46
|
+
/// @notice Map of state serializers for different price feed types
|
|
47
|
+
/// @dev Serializers only apply to feeds that don't implement `IStateSerializerTrait` themselves
|
|
48
|
+
mapping(uint8 => address) public serializers;
|
|
49
|
+
|
|
50
|
+
/// @notice Emitted when new state serializer is set for a given price feed type
|
|
51
|
+
event SetSerializer(uint8 indexed priceFeedType, address indexed serializer);
|
|
52
|
+
|
|
53
|
+
/// @notice Constructor
|
|
54
|
+
/// @param owner Contract owner
|
|
55
|
+
/// @dev Sets serializers for existing price feed types.
|
|
56
|
+
/// It is recommended to implement `IStateSerializerTrait` in new price feeds.
|
|
57
|
+
constructor(address owner) {
|
|
58
|
+
transferOwnership(owner);
|
|
59
|
+
|
|
60
|
+
address lpSerializer = address(new LPPriceFeedSerializer());
|
|
61
|
+
// these types can be serialized as generic LP price feeds
|
|
62
|
+
_setSerializer(uint8(PriceFeedType.BALANCER_STABLE_LP_ORACLE), lpSerializer);
|
|
63
|
+
_setSerializer(uint8(PriceFeedType.COMPOUND_V2_ORACLE), lpSerializer);
|
|
64
|
+
_setSerializer(uint8(PriceFeedType.CURVE_2LP_ORACLE), lpSerializer);
|
|
65
|
+
_setSerializer(uint8(PriceFeedType.CURVE_3LP_ORACLE), lpSerializer);
|
|
66
|
+
_setSerializer(uint8(PriceFeedType.CURVE_4LP_ORACLE), lpSerializer);
|
|
67
|
+
_setSerializer(uint8(PriceFeedType.CURVE_CRYPTO_ORACLE), lpSerializer);
|
|
68
|
+
_setSerializer(uint8(PriceFeedType.CURVE_USD_ORACLE), lpSerializer);
|
|
69
|
+
_setSerializer(uint8(PriceFeedType.ERC4626_VAULT_ORACLE), lpSerializer);
|
|
70
|
+
_setSerializer(uint8(PriceFeedType.WRAPPED_AAVE_V2_ORACLE), lpSerializer);
|
|
71
|
+
_setSerializer(uint8(PriceFeedType.WSTETH_ORACLE), lpSerializer);
|
|
72
|
+
_setSerializer(uint8(PriceFeedType.YEARN_ORACLE), lpSerializer);
|
|
73
|
+
|
|
74
|
+
// these types need special serialization
|
|
75
|
+
_setSerializer(uint8(PriceFeedType.BALANCER_WEIGHTED_LP_ORACLE), address(new BPTWeightedPriceFeedSerializer()));
|
|
76
|
+
_setSerializer(uint8(PriceFeedType.BOUNDED_ORACLE), address(new BoundedPriceFeedSerializer()));
|
|
77
|
+
_setSerializer(uint8(PriceFeedType.PYTH_ORACLE), address(new PythPriceFeedSerializer()));
|
|
78
|
+
_setSerializer(uint8(PriceFeedType.REDSTONE_ORACLE), address(new RedstonePriceFeedSerializer()));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// @notice Sets state serializer for a given price feed type (unsets if `serializer` is `address(0)`)
|
|
82
|
+
function setSerializer(uint8 priceFeedType, address serializer) external onlyOwner {
|
|
83
|
+
if (serializer != address(0) && serializer.code.length == 0) revert AddressIsNotContractException(serializer);
|
|
84
|
+
_setSerializer(priceFeedType, serializer);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// @notice Returns all potentially useful price feeds data for a given price oracle in the form of two arrays:
|
|
88
|
+
/// - `priceFeedMap` is a set of entries in the map (token, reserve) => (priceFeed, stalenessPeirod).
|
|
89
|
+
/// These are all the price feeds one can actually query via the price oracle.
|
|
90
|
+
/// - `priceFeedTree` is a set of nodes in a tree-like structure that contains detailed info of both feeds
|
|
91
|
+
/// from `priceFeedMap` and their underlying feeds, in case former are nested, which can help to determine
|
|
92
|
+
/// what underlying feeds should be updated to query the nested one.
|
|
93
|
+
/// @dev `priceFeedTree` can have duplicate entries since a price feed can both be in `priceFeedMap` for one or
|
|
94
|
+
/// more (token, reserve) pairs, and serve as an underlying feed in one or more nested feeds.
|
|
95
|
+
/// If there are two identical nodes in the tree, then subtrees of these nodes are also identical.
|
|
96
|
+
function getPriceFeeds(address priceOracle)
|
|
97
|
+
external
|
|
98
|
+
view
|
|
99
|
+
returns (PriceFeedMapEntry[] memory priceFeedMap, PriceFeedTreeNode[] memory priceFeedTree)
|
|
100
|
+
{
|
|
101
|
+
address[] memory tokens = IPriceOracleV3(priceOracle).getTokens();
|
|
102
|
+
return getPriceFeeds(priceOracle, tokens);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// @dev Same as the above but takes the list of tokens as argument as legacy oracle doesn't implement `getTokens`
|
|
106
|
+
function getPriceFeeds(address priceOracle, address[] memory tokens)
|
|
107
|
+
public
|
|
108
|
+
view
|
|
109
|
+
returns (PriceFeedMapEntry[] memory priceFeedMap, PriceFeedTreeNode[] memory priceFeedTree)
|
|
110
|
+
{
|
|
111
|
+
uint256 numTokens = tokens.length;
|
|
112
|
+
|
|
113
|
+
priceFeedMap = new PriceFeedMapEntry[](2 * numTokens);
|
|
114
|
+
uint256 priceFeedMapSize;
|
|
115
|
+
uint256 priceFeedTreeSize;
|
|
116
|
+
|
|
117
|
+
for (uint256 i; i < 2 * numTokens; ++i) {
|
|
118
|
+
address token = tokens[i % numTokens];
|
|
119
|
+
bool reserve = i >= numTokens;
|
|
120
|
+
|
|
121
|
+
(address priceFeed, uint32 stalenessPeriod) = _getPriceFeed(priceOracle, token, reserve);
|
|
122
|
+
if (priceFeed == address(0)) continue;
|
|
123
|
+
|
|
124
|
+
priceFeedMap[priceFeedMapSize++] = PriceFeedMapEntry({
|
|
125
|
+
token: token,
|
|
126
|
+
reserve: reserve,
|
|
127
|
+
priceFeed: priceFeed,
|
|
128
|
+
stalenessPeriod: stalenessPeriod
|
|
129
|
+
});
|
|
130
|
+
priceFeedTreeSize += _getPriceFeedTreeSize(priceFeed);
|
|
131
|
+
}
|
|
132
|
+
assembly {
|
|
133
|
+
mstore(priceFeedMap, priceFeedMapSize)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
priceFeedTree = new PriceFeedTreeNode[](priceFeedTreeSize);
|
|
137
|
+
uint256 offset;
|
|
138
|
+
for (uint256 i; i < priceFeedMapSize; ++i) {
|
|
139
|
+
offset = _loadPriceFeedTree(priceFeedMap[i].priceFeed, priceFeedTree, offset);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --------- //
|
|
144
|
+
// INTERNALS //
|
|
145
|
+
// --------- //
|
|
146
|
+
|
|
147
|
+
/// @dev Sets `serializer` for `priceFeedType`
|
|
148
|
+
function _setSerializer(uint8 priceFeedType, address serializer) internal {
|
|
149
|
+
if (serializers[priceFeedType] != serializer) {
|
|
150
|
+
serializers[priceFeedType] = serializer;
|
|
151
|
+
emit SetSerializer(priceFeedType, serializer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// @dev Returns `token`'s price feed in the price oracle
|
|
156
|
+
function _getPriceFeed(address priceOracle, address token, bool reserve) internal view returns (address, uint32) {
|
|
157
|
+
if (IPriceOracleV3(priceOracle).version() < 3_10) {
|
|
158
|
+
address priceFeed = IPriceOracleV3Legacy(priceOracle).priceFeedsRaw(token, reserve);
|
|
159
|
+
// legacy oracle does not allow to fetch staleness period of a non-active feed
|
|
160
|
+
return (priceFeed, 0);
|
|
161
|
+
}
|
|
162
|
+
PriceFeedParams memory params = reserve
|
|
163
|
+
? IPriceOracleV3(priceOracle).reservePriceFeedParams(token)
|
|
164
|
+
: IPriceOracleV3(priceOracle).priceFeedParams(token);
|
|
165
|
+
return (params.priceFeed, params.stalenessPeriod);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// @dev Computes the size of the `priceFeed`'s subtree (recursively)
|
|
169
|
+
function _getPriceFeedTreeSize(address priceFeed) internal view returns (uint256 size) {
|
|
170
|
+
size = 1;
|
|
171
|
+
(address[] memory underlyingFeeds,) = IPriceFeed(priceFeed).getUnderlyingFeeds();
|
|
172
|
+
for (uint256 i; i < underlyingFeeds.length; ++i) {
|
|
173
|
+
size += _getPriceFeedTreeSize(underlyingFeeds[i]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// @dev Loads `priceFeed`'s subtree (recursively)
|
|
178
|
+
function _loadPriceFeedTree(address priceFeed, PriceFeedTreeNode[] memory priceFeedTree, uint256 offset)
|
|
179
|
+
internal
|
|
180
|
+
view
|
|
181
|
+
returns (uint256)
|
|
182
|
+
{
|
|
183
|
+
PriceFeedTreeNode memory node = _getPriceFeedTreeNode(priceFeed);
|
|
184
|
+
priceFeedTree[offset++] = node;
|
|
185
|
+
for (uint256 i; i < node.underlyingFeeds.length; ++i) {
|
|
186
|
+
offset = _loadPriceFeedTree(node.underlyingFeeds[i], priceFeedTree, offset);
|
|
187
|
+
}
|
|
188
|
+
return offset;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// @dev Returns price feed tree node, see `PriceFeedTreeNode` for detailed description of struct fields
|
|
192
|
+
function _getPriceFeedTreeNode(address priceFeed) internal view returns (PriceFeedTreeNode memory data) {
|
|
193
|
+
data.priceFeed = priceFeed;
|
|
194
|
+
data.decimals = IPriceFeed(priceFeed).decimals();
|
|
195
|
+
|
|
196
|
+
try ImplementsPriceFeedType(priceFeed).priceFeedType() returns (uint8 priceFeedType) {
|
|
197
|
+
data.priceFeedType = priceFeedType;
|
|
198
|
+
} catch {
|
|
199
|
+
data.priceFeedType = uint8(PriceFeedType.CHAINLINK_ORACLE);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try IPriceFeed(priceFeed).skipPriceCheck() returns (bool skipCheck) {
|
|
203
|
+
data.skipCheck = skipCheck;
|
|
204
|
+
} catch {}
|
|
205
|
+
|
|
206
|
+
try IUpdatablePriceFeed(priceFeed).updatable() returns (bool updatable) {
|
|
207
|
+
data.updatable = updatable;
|
|
208
|
+
} catch {}
|
|
209
|
+
|
|
210
|
+
try IStateSerializerTrait(priceFeed).serialize() returns (bytes memory specificParams) {
|
|
211
|
+
data.specificParams = specificParams;
|
|
212
|
+
} catch {
|
|
213
|
+
address serializer = serializers[data.priceFeedType];
|
|
214
|
+
if (serializer != address(0)) {
|
|
215
|
+
data.specificParams = IStateSerializer(serializer).serialize(priceFeed);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
(data.underlyingFeeds, data.underlyingStalenessPeriods) = IPriceFeed(priceFeed).getUnderlyingFeeds();
|
|
220
|
+
|
|
221
|
+
try IPriceFeed(priceFeed).latestRoundData() returns (uint80, int256 price, uint256, uint256 updatedAt, uint80) {
|
|
222
|
+
data.answer = PriceFeedAnswer({price: price, updatedAt: updatedAt, success: true});
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
/// @notice Credit account data
|
|
7
|
+
/// @param creditAccount Credit account address
|
|
8
|
+
/// @param creditManager Credit manager account is opened in
|
|
9
|
+
/// @param creditFacade Facade connected to account's credit manager
|
|
10
|
+
/// @param underlying Credit manager's underlying token
|
|
11
|
+
/// @param owner Credit account owner
|
|
12
|
+
/// @param enabledTokensMask Bitmask of tokens enabled on credit account as collateral
|
|
13
|
+
/// @param debt Credit account's debt principal in underlying
|
|
14
|
+
/// @param accruedInterest Base and quota interest accrued on the credit account
|
|
15
|
+
/// @param accruedFees Fees accrued on the credit account
|
|
16
|
+
/// @param totalDebtUSD Account's total debt in USD
|
|
17
|
+
/// @param totalValueUSD Account's total value in USD
|
|
18
|
+
/// @param twvUSD Account's threshold-weighted value in USD
|
|
19
|
+
/// @param totalValue Account's total value in underlying
|
|
20
|
+
/// @param healthFactor Account's health factor, i.e. ratio of `twvUSD` to `totalDebtUSD` with 18 decimals precision
|
|
21
|
+
/// @param success Whether collateral calculation was successful
|
|
22
|
+
/// @param tokens Info on all collateral tokens in account's credit manager ordered by their mask, see `TokenInfo`
|
|
23
|
+
/// @dev Fields from `totalDebtUSD` through `healthFactor` are not filled if `success` is `false`
|
|
24
|
+
/// @dev `debt`, `accruedInterest` and `accruedFees` don't account for transfer fees
|
|
25
|
+
struct CreditAccountData {
|
|
26
|
+
address creditAccount;
|
|
27
|
+
address creditManager;
|
|
28
|
+
address creditFacade;
|
|
29
|
+
address underlying;
|
|
30
|
+
address owner;
|
|
31
|
+
uint256 enabledTokensMask;
|
|
32
|
+
uint256 debt;
|
|
33
|
+
uint256 accruedInterest;
|
|
34
|
+
uint256 accruedFees;
|
|
35
|
+
uint256 totalDebtUSD;
|
|
36
|
+
uint256 totalValueUSD;
|
|
37
|
+
uint256 twvUSD;
|
|
38
|
+
uint256 totalValue;
|
|
39
|
+
uint256 healthFactor;
|
|
40
|
+
bool success;
|
|
41
|
+
TokenInfo[] tokens;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @notice Credit account filters
|
|
45
|
+
/// @param owner If set, match credit accounts owned by given address
|
|
46
|
+
/// @param includeZeroDebt If set, also match accounts with zero debt
|
|
47
|
+
/// @param minHealthFactor If set, only return accounts with health factor above this value
|
|
48
|
+
/// @param maxHealthFactor If set, only return accounts with health factor below this value
|
|
49
|
+
/// @param reverting If set, only match accounts with reverting collateral calculation
|
|
50
|
+
struct CreditAccountFilter {
|
|
51
|
+
address owner;
|
|
52
|
+
bool includeZeroDebt;
|
|
53
|
+
uint256 minHealthFactor;
|
|
54
|
+
uint256 maxHealthFactor;
|
|
55
|
+
bool reverting;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// @notice Credit manager filters
|
|
59
|
+
/// @param curator If set, match credit managers managed by given curator
|
|
60
|
+
/// @param pool If set, match credit managers connected to a given pool
|
|
61
|
+
/// @param underlying If set, match credit managers with given underlying
|
|
62
|
+
struct CreditManagerFilter {
|
|
63
|
+
address curator;
|
|
64
|
+
address pool;
|
|
65
|
+
address underlying;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// @notice Price feed answer packed in a struct
|
|
69
|
+
struct PriceFeedAnswer {
|
|
70
|
+
int256 price;
|
|
71
|
+
uint256 updatedAt;
|
|
72
|
+
bool success;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @notice Represents an entry in the price feed map of a price oracle
|
|
76
|
+
/// @dev `stalenessPeriod` is always 0 if price oracle's version is below `3_10`
|
|
77
|
+
struct PriceFeedMapEntry {
|
|
78
|
+
address token;
|
|
79
|
+
bool reserve;
|
|
80
|
+
address priceFeed;
|
|
81
|
+
uint32 stalenessPeriod;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// @notice Represents a node in the price feed "tree"
|
|
85
|
+
/// @param priceFeed Price feed address
|
|
86
|
+
/// @param decimals Price feed's decimals (might not be equal to 8 for lower-level)
|
|
87
|
+
/// @param priceFeedType Price feed type (same as `PriceFeedType` but annotated as `uint8` to support future types),
|
|
88
|
+
/// defaults to `PriceFeedType.CHAINLINK_ORACLE`
|
|
89
|
+
/// @param skipCheck Whether price feed implements its own staleness and sanity check, defaults to `false`
|
|
90
|
+
/// @param updatable Whether it is an on-demand updatable (aka pull) price feed, defaults to `false`
|
|
91
|
+
/// @param specificParams ABI-encoded params specific to this price feed type, filled if price feed implements
|
|
92
|
+
/// `IStateSerializerTrait` or there is a state serializer set for this type
|
|
93
|
+
/// @param underlyingFeeds Array of underlying feeds, filled when `priceFeed` is nested
|
|
94
|
+
/// @param underlyingStalenessPeriods Staleness periods of underlying feeds, filled when `priceFeed` is nested
|
|
95
|
+
/// @param answer Price feed answer packed in a struct
|
|
96
|
+
struct PriceFeedTreeNode {
|
|
97
|
+
address priceFeed;
|
|
98
|
+
uint8 decimals;
|
|
99
|
+
uint8 priceFeedType;
|
|
100
|
+
bool skipCheck;
|
|
101
|
+
bool updatable;
|
|
102
|
+
bytes specificParams;
|
|
103
|
+
address[] underlyingFeeds;
|
|
104
|
+
uint32[] underlyingStalenessPeriods;
|
|
105
|
+
PriceFeedAnswer answer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// @notice Info on credit account's holdings of a token
|
|
109
|
+
/// @param token Token address
|
|
110
|
+
/// @param balance Account's balance of token
|
|
111
|
+
/// @param quota Account's quota of token
|
|
112
|
+
/// @param success Whether balance call was successful
|
|
113
|
+
struct TokenInfo {
|
|
114
|
+
address token;
|
|
115
|
+
uint256 balance;
|
|
116
|
+
uint256 quota;
|
|
117
|
+
bool success;
|
|
118
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
/// @title State serializer
|
|
7
|
+
/// @notice Generic interface of a contract that is able to serialize state of other contracts
|
|
8
|
+
interface IStateSerializer {
|
|
9
|
+
function serialize(address) external view returns (bytes memory);
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
/// @title State serializer trait
|
|
7
|
+
/// @notice Generic interface of a contract that is able to serialize its own state
|
|
8
|
+
interface IStateSerializerTrait {
|
|
9
|
+
function serialize() external view returns (bytes memory);
|
|
10
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {IPriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol";
|
|
7
|
+
|
|
8
|
+
uint256 constant MAX_UNDERLYING_PRICE_FEEDS = 8;
|
|
9
|
+
|
|
10
|
+
interface NestedPriceFeedWithSingleUnderlying is IPriceFeed {
|
|
11
|
+
function priceFeed() external view returns (address);
|
|
12
|
+
function stalenessPeriod() external view returns (uint32);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface NestedPriceFeedWithMultipleUnderlyings is IPriceFeed {
|
|
16
|
+
function priceFeed0() external view returns (address);
|
|
17
|
+
function priceFeed1() external view returns (address);
|
|
18
|
+
function priceFeed2() external view returns (address);
|
|
19
|
+
function priceFeed3() external view returns (address);
|
|
20
|
+
function priceFeed4() external view returns (address);
|
|
21
|
+
function priceFeed5() external view returns (address);
|
|
22
|
+
function priceFeed6() external view returns (address);
|
|
23
|
+
function priceFeed7() external view returns (address);
|
|
24
|
+
|
|
25
|
+
function stalenessPeriod0() external view returns (uint32);
|
|
26
|
+
function stalenessPeriod1() external view returns (uint32);
|
|
27
|
+
function stalenessPeriod2() external view returns (uint32);
|
|
28
|
+
function stalenessPeriod3() external view returns (uint32);
|
|
29
|
+
function stalenessPeriod4() external view returns (uint32);
|
|
30
|
+
function stalenessPeriod5() external view returns (uint32);
|
|
31
|
+
function stalenessPeriod6() external view returns (uint32);
|
|
32
|
+
function stalenessPeriod7() external view returns (uint32);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
library NestedPriceFeeds {
|
|
36
|
+
enum NestingType {
|
|
37
|
+
NO_NESTING,
|
|
38
|
+
SINGLE_UNDERLYING,
|
|
39
|
+
MULTIPLE_UNDERLYING
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getUnderlyingFeeds(IPriceFeed priceFeed)
|
|
43
|
+
internal
|
|
44
|
+
view
|
|
45
|
+
returns (address[] memory feeds, uint32[] memory stalenessPeriods)
|
|
46
|
+
{
|
|
47
|
+
NestingType nestingType = getNestingType(priceFeed);
|
|
48
|
+
if (nestingType == NestingType.SINGLE_UNDERLYING) {
|
|
49
|
+
(feeds, stalenessPeriods) = getSingleUnderlyingFeed(NestedPriceFeedWithSingleUnderlying(address(priceFeed)));
|
|
50
|
+
} else if (nestingType == NestingType.MULTIPLE_UNDERLYING) {
|
|
51
|
+
(feeds, stalenessPeriods) =
|
|
52
|
+
getMultipleUnderlyingFeeds(NestedPriceFeedWithMultipleUnderlyings(address(priceFeed)));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getNestingType(IPriceFeed priceFeed) internal view returns (NestingType) {
|
|
57
|
+
try NestedPriceFeedWithSingleUnderlying(address(priceFeed)).priceFeed() returns (address) {
|
|
58
|
+
return NestingType.SINGLE_UNDERLYING;
|
|
59
|
+
} catch {}
|
|
60
|
+
|
|
61
|
+
try NestedPriceFeedWithMultipleUnderlyings(address(priceFeed)).priceFeed0() returns (address) {
|
|
62
|
+
return NestingType.MULTIPLE_UNDERLYING;
|
|
63
|
+
} catch {}
|
|
64
|
+
|
|
65
|
+
return NestingType.NO_NESTING;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getSingleUnderlyingFeed(NestedPriceFeedWithSingleUnderlying priceFeed)
|
|
69
|
+
internal
|
|
70
|
+
view
|
|
71
|
+
returns (address[] memory feeds, uint32[] memory stalenessPeriods)
|
|
72
|
+
{
|
|
73
|
+
feeds = new address[](1);
|
|
74
|
+
stalenessPeriods = new uint32[](1);
|
|
75
|
+
(feeds[0], stalenessPeriods[0]) = (priceFeed.priceFeed(), priceFeed.stalenessPeriod());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getMultipleUnderlyingFeeds(NestedPriceFeedWithMultipleUnderlyings priceFeed)
|
|
79
|
+
internal
|
|
80
|
+
view
|
|
81
|
+
returns (address[] memory feeds, uint32[] memory stalenessPeriods)
|
|
82
|
+
{
|
|
83
|
+
feeds = new address[](MAX_UNDERLYING_PRICE_FEEDS);
|
|
84
|
+
stalenessPeriods = new uint32[](MAX_UNDERLYING_PRICE_FEEDS);
|
|
85
|
+
for (uint256 i; i < MAX_UNDERLYING_PRICE_FEEDS; ++i) {
|
|
86
|
+
feeds[i] = _getPriceFeedByIndex(priceFeed, i);
|
|
87
|
+
if (feeds[i] == address(0)) {
|
|
88
|
+
assembly {
|
|
89
|
+
mstore(feeds, i)
|
|
90
|
+
mstore(stalenessPeriods, i)
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
stalenessPeriods[i] = _getStalenessPeriodByIndex(priceFeed, i);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _getPriceFeedByIndex(NestedPriceFeedWithMultipleUnderlyings priceFeed, uint256 index)
|
|
99
|
+
private
|
|
100
|
+
view
|
|
101
|
+
returns (address)
|
|
102
|
+
{
|
|
103
|
+
bytes4 selector;
|
|
104
|
+
if (index == 0) {
|
|
105
|
+
selector = priceFeed.priceFeed0.selector;
|
|
106
|
+
} else if (index == 1) {
|
|
107
|
+
selector = priceFeed.priceFeed1.selector;
|
|
108
|
+
} else if (index == 2) {
|
|
109
|
+
selector = priceFeed.priceFeed2.selector;
|
|
110
|
+
} else if (index == 3) {
|
|
111
|
+
selector = priceFeed.priceFeed3.selector;
|
|
112
|
+
} else if (index == 4) {
|
|
113
|
+
selector = priceFeed.priceFeed4.selector;
|
|
114
|
+
} else if (index == 5) {
|
|
115
|
+
selector = priceFeed.priceFeed5.selector;
|
|
116
|
+
} else if (index == 6) {
|
|
117
|
+
selector = priceFeed.priceFeed6.selector;
|
|
118
|
+
} else if (index == 7) {
|
|
119
|
+
selector = priceFeed.priceFeed7.selector;
|
|
120
|
+
}
|
|
121
|
+
(bool success, bytes memory result) = address(priceFeed).staticcall(abi.encodePacked(selector));
|
|
122
|
+
if (!success || result.length == 0) return address(0);
|
|
123
|
+
return abi.decode(result, (address));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function _getStalenessPeriodByIndex(NestedPriceFeedWithMultipleUnderlyings priceFeed, uint256 index)
|
|
127
|
+
private
|
|
128
|
+
view
|
|
129
|
+
returns (uint32)
|
|
130
|
+
{
|
|
131
|
+
bytes4 selector;
|
|
132
|
+
if (index == 0) {
|
|
133
|
+
selector = priceFeed.stalenessPeriod0.selector;
|
|
134
|
+
} else if (index == 1) {
|
|
135
|
+
selector = priceFeed.stalenessPeriod1.selector;
|
|
136
|
+
} else if (index == 2) {
|
|
137
|
+
selector = priceFeed.stalenessPeriod2.selector;
|
|
138
|
+
} else if (index == 3) {
|
|
139
|
+
selector = priceFeed.stalenessPeriod3.selector;
|
|
140
|
+
} else if (index == 4) {
|
|
141
|
+
selector = priceFeed.stalenessPeriod4.selector;
|
|
142
|
+
} else if (index == 5) {
|
|
143
|
+
selector = priceFeed.stalenessPeriod5.selector;
|
|
144
|
+
} else if (index == 6) {
|
|
145
|
+
selector = priceFeed.stalenessPeriod6.selector;
|
|
146
|
+
} else if (index == 7) {
|
|
147
|
+
selector = priceFeed.stalenessPeriod7.selector;
|
|
148
|
+
}
|
|
149
|
+
(bool success, bytes memory result) = address(priceFeed).staticcall(abi.encodePacked(selector));
|
|
150
|
+
if (!success || result.length == 0) return 0;
|
|
151
|
+
return abi.decode(result, (uint32));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {BPTWeightedPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/balancer/BPTWeightedPriceFeed.sol";
|
|
7
|
+
import {LPPriceFeedSerializer} from "./LPPriceFeedSerializer.sol";
|
|
8
|
+
|
|
9
|
+
contract BPTWeightedPriceFeedSerializer is LPPriceFeedSerializer {
|
|
10
|
+
function serialize(address priceFeed) public view override returns (bytes memory) {
|
|
11
|
+
BPTWeightedPriceFeed pf = BPTWeightedPriceFeed(priceFeed);
|
|
12
|
+
|
|
13
|
+
uint256[8] memory weights = [
|
|
14
|
+
pf.weight0(),
|
|
15
|
+
pf.weight1(),
|
|
16
|
+
pf.weight2(),
|
|
17
|
+
pf.weight3(),
|
|
18
|
+
pf.weight4(),
|
|
19
|
+
pf.weight5(),
|
|
20
|
+
pf.weight6(),
|
|
21
|
+
pf.weight7()
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
return abi.encode(super.serialize(priceFeed), pf.vault(), pf.poolId(), weights);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {BoundedPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/BoundedPriceFeed.sol";
|
|
7
|
+
import {IStateSerializer} from "../../interfaces/IStateSerializer.sol";
|
|
8
|
+
|
|
9
|
+
contract BoundedPriceFeedSerializer is IStateSerializer {
|
|
10
|
+
function serialize(address priceFeed) external view override returns (bytes memory) {
|
|
11
|
+
BoundedPriceFeed pf = BoundedPriceFeed(priceFeed);
|
|
12
|
+
|
|
13
|
+
return abi.encode(pf.upperBound());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {ILPPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/interfaces/ILPPriceFeed.sol";
|
|
7
|
+
import {IStateSerializer} from "../../interfaces/IStateSerializer.sol";
|
|
8
|
+
|
|
9
|
+
contract LPPriceFeedSerializer is IStateSerializer {
|
|
10
|
+
struct PriceData {
|
|
11
|
+
uint256 exchangeRate;
|
|
12
|
+
int256 aggregatePrice;
|
|
13
|
+
uint256 scale;
|
|
14
|
+
bool exchageRateSuccess;
|
|
15
|
+
bool aggregatePriceSuccess;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function serialize(address priceFeed) public view virtual override returns (bytes memory) {
|
|
19
|
+
ILPPriceFeed pf = ILPPriceFeed(priceFeed);
|
|
20
|
+
|
|
21
|
+
return abi.encode(
|
|
22
|
+
pf.lpToken(),
|
|
23
|
+
pf.lpContract(),
|
|
24
|
+
pf.lowerBound(),
|
|
25
|
+
pf.upperBound(),
|
|
26
|
+
pf.updateBoundsAllowed(),
|
|
27
|
+
pf.lastBoundsUpdate(),
|
|
28
|
+
_getPriceData(pf)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _getPriceData(ILPPriceFeed priceFeed) internal view returns (PriceData memory data) {
|
|
33
|
+
try priceFeed.getLPExchangeRate() returns (uint256 rate) {
|
|
34
|
+
data.exchangeRate = rate;
|
|
35
|
+
data.exchageRateSuccess = true;
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
try priceFeed.getAggregatePrice() returns (int256 price) {
|
|
39
|
+
data.aggregatePrice = price;
|
|
40
|
+
data.aggregatePriceSuccess = true;
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
// safe to assume that `getScale` is non-reverting
|
|
44
|
+
data.scale = priceFeed.getScale();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {PythPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/updatable/PythPriceFeed.sol";
|
|
7
|
+
import {IStateSerializer} from "../../interfaces/IStateSerializer.sol";
|
|
8
|
+
|
|
9
|
+
contract PythPriceFeedSerializer is IStateSerializer {
|
|
10
|
+
function serialize(address priceFeed) external view override returns (bytes memory) {
|
|
11
|
+
PythPriceFeed pf = PythPriceFeed(payable(priceFeed));
|
|
12
|
+
|
|
13
|
+
return abi.encode(pf.token(), pf.priceFeedId(), pf.pyth());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Gearbox Protocol. Generalized leverage for DeFi protocols
|
|
3
|
+
// (c) Gearbox Foundation, 2024.
|
|
4
|
+
pragma solidity ^0.8.17;
|
|
5
|
+
|
|
6
|
+
import {RedstonePriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/updatable/RedstonePriceFeed.sol";
|
|
7
|
+
import {IStateSerializer} from "../../interfaces/IStateSerializer.sol";
|
|
8
|
+
|
|
9
|
+
contract RedstonePriceFeedSerializer is IStateSerializer {
|
|
10
|
+
function serialize(address priceFeed) external view override returns (bytes memory) {
|
|
11
|
+
RedstonePriceFeed pf = RedstonePriceFeed(priceFeed);
|
|
12
|
+
|
|
13
|
+
address[10] memory signers = [
|
|
14
|
+
pf.signerAddress0(),
|
|
15
|
+
pf.signerAddress1(),
|
|
16
|
+
pf.signerAddress2(),
|
|
17
|
+
pf.signerAddress3(),
|
|
18
|
+
pf.signerAddress4(),
|
|
19
|
+
pf.signerAddress5(),
|
|
20
|
+
pf.signerAddress6(),
|
|
21
|
+
pf.signerAddress7(),
|
|
22
|
+
pf.signerAddress8(),
|
|
23
|
+
pf.signerAddress9()
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return abi.encode(
|
|
27
|
+
pf.token(),
|
|
28
|
+
pf.dataFeedId(),
|
|
29
|
+
signers,
|
|
30
|
+
pf.getUniqueSignersThreshold(),
|
|
31
|
+
pf.lastPrice(),
|
|
32
|
+
pf.lastPayloadTimestamp()
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gearbox-protocol/periphery-v3",
|
|
3
|
-
"version": "1.7.0-next.
|
|
3
|
+
"version": "1.7.0-next.4",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": "git@github.com:Gearbox-protocol/periphery-v3.git",
|
|
6
6
|
"author": "Mikael <26343374+0xmikko@users.noreply.github.com>",
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@1inch/solidity-utils": "^2.2.27",
|
|
18
|
+
"@chainlink/contracts": "^0.4.0",
|
|
18
19
|
"@commitlint/cli": "^17.6.3",
|
|
19
20
|
"@commitlint/config-conventional": "17.6.0",
|
|
20
|
-
"@gearbox-protocol/core-v3": "^1.50.0-next.
|
|
21
|
+
"@gearbox-protocol/core-v3": "^1.50.0-next.15",
|
|
22
|
+
"@gearbox-protocol/governance": "^1.4.0-next.5",
|
|
21
23
|
"@gearbox-protocol/integrations-v3": "^1.23.1",
|
|
22
|
-
"@gearbox-protocol/oracles-v3": "^1.11.0-next.
|
|
24
|
+
"@gearbox-protocol/oracles-v3": "^1.11.0-next.3",
|
|
23
25
|
"@gearbox-protocol/sdk-gov": "^2.3.1",
|
|
24
26
|
"@openzeppelin/contracts": "^4.9.3",
|
|
27
|
+
"@redstone-finance/evm-connector": "0.2.5",
|
|
25
28
|
"ds-test": "dapphub/ds-test",
|
|
26
29
|
"forge-std": "foundry-rs/forge-std"
|
|
27
30
|
},
|