@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.
@@ -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.1",
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.14",
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.2",
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
  },