@gearbox-protocol/periphery-v3 1.7.0-next.1 → 1.7.0-next.11

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,402 @@
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
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
8
+
9
+ import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol";
10
+ import {ICreditAccountV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditAccountV3.sol";
11
+ import {
12
+ CollateralCalcTask,
13
+ CollateralDebtData,
14
+ ICreditManagerV3
15
+ } from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
16
+ import {IPoolQuotaKeeperV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPoolQuotaKeeperV3.sol";
17
+ import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
18
+ import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
19
+ import {SanityCheckTrait} from "@gearbox-protocol/core-v3/contracts/traits/SanityCheckTrait.sol";
20
+
21
+ import {IAddressProviderV3} from "@gearbox-protocol/governance/contracts/interfaces/IAddressProviderV3.sol";
22
+ import {IMarketConfiguratorV3} from "@gearbox-protocol/governance/contracts/interfaces/IMarketConfiguratorV3.sol";
23
+
24
+ import {CreditAccountData, CreditAccountFilter, CreditManagerFilter, TokenInfo} from "./Types.sol";
25
+
26
+ /// @title Credit account compressor
27
+ /// @notice Allows to fetch data on all credit accounts matching certain criteria in an efficient manner
28
+ /// @dev The contract is not gas optimized and is thus not recommended for on-chain use
29
+ /// @dev Querying functions try to process as many accounts as possible and stop when they get close to gas limit
30
+ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
31
+ /// @notice Contract version
32
+ uint256 public constant override version = 3_10;
33
+
34
+ /// @notice Address provider contract address
35
+ address public immutable ADDRESS_PROVIDER;
36
+
37
+ /// @notice Thrown when address provider is not a contract or does not implement `marketConfigurators()`
38
+ error InvalidAddressProviderException();
39
+
40
+ /// @notice Constructor
41
+ /// @param addressProvider Address provider contract address
42
+ constructor(address addressProvider) nonZeroAddress(addressProvider) {
43
+ if (addressProvider.code.length == 0) revert InvalidAddressProviderException();
44
+ try IAddressProviderV3(addressProvider).marketConfigurators() {}
45
+ catch {
46
+ revert InvalidAddressProviderException();
47
+ }
48
+ ADDRESS_PROVIDER = addressProvider;
49
+ }
50
+
51
+ // -------- //
52
+ // QUERYING //
53
+ // -------- //
54
+
55
+ /// @notice Returns data for a particular `creditAccount`
56
+ function getCreditAccountData(address creditAccount) external view returns (CreditAccountData memory) {
57
+ address creditManager = ICreditAccountV3(creditAccount).creditManager();
58
+ return _getCreditAccountData(creditAccount, creditManager);
59
+ }
60
+
61
+ /// @notice Returns data for credit accounts that match `caFilter` in credit managers matching `cmFilter`
62
+ /// @dev The non-zero value of `nextOffset` return variable indicates that gas supplied with a call was
63
+ /// insufficient to process all the accounts and next iteration starting from this value is needed
64
+ function getCreditAccounts(CreditManagerFilter memory cmFilter, CreditAccountFilter memory caFilter, uint256 offset)
65
+ external
66
+ view
67
+ returns (CreditAccountData[] memory data, uint256 nextOffset)
68
+ {
69
+ address[] memory creditManagers = _getCreditManagers(cmFilter);
70
+ return _getCreditAccounts(creditManagers, caFilter, offset, type(uint256).max);
71
+ }
72
+
73
+ /// @dev Same as above but with `limit` parameter that specifies the number of accounts to process
74
+ function getCreditAccounts(
75
+ CreditManagerFilter memory cmFilter,
76
+ CreditAccountFilter memory caFilter,
77
+ uint256 offset,
78
+ uint256 limit
79
+ ) public view returns (CreditAccountData[] memory data, uint256 nextOffset) {
80
+ address[] memory creditManagers = _getCreditManagers(cmFilter);
81
+ return _getCreditAccounts(creditManagers, caFilter, offset, limit);
82
+ }
83
+
84
+ /// @notice Returns data for credit accounts that match `caFilter` in a given `creditManager`
85
+ /// @dev The non-zero value of `nextOffset` return variable indicates that gas supplied with a call was
86
+ /// insufficient to process all the accounts and next iteration starting from this value is needed
87
+ function getCreditAccounts(address creditManager, CreditAccountFilter memory caFilter, uint256 offset)
88
+ external
89
+ view
90
+ returns (CreditAccountData[] memory data, uint256 nextOffset)
91
+ {
92
+ address[] memory creditManagers = new address[](1);
93
+ creditManagers[0] = creditManager;
94
+ return _getCreditAccounts(creditManagers, caFilter, offset, type(uint256).max);
95
+ }
96
+
97
+ /// @dev Same as above but with `limit` parameter that specifies the number of accounts to process
98
+ function getCreditAccounts(
99
+ address creditManager,
100
+ CreditAccountFilter memory caFilter,
101
+ uint256 offset,
102
+ uint256 limit
103
+ ) external view returns (CreditAccountData[] memory data, uint256 nextOffset) {
104
+ address[] memory creditManagers = new address[](1);
105
+ creditManagers[0] = creditManager;
106
+ return _getCreditAccounts(creditManagers, caFilter, offset, limit);
107
+ }
108
+
109
+ // -------- //
110
+ // COUNTING //
111
+ // -------- //
112
+
113
+ /// @notice Counts credit accounts that match `caFilter` in credit managers matching `cmFilter`
114
+ function countCreditAccounts(CreditManagerFilter memory cmFilter, CreditAccountFilter memory caFilter)
115
+ external
116
+ view
117
+ returns (uint256)
118
+ {
119
+ address[] memory creditManagers = _getCreditManagers(cmFilter);
120
+ return _countCreditAccounts(creditManagers, caFilter, 0, type(uint256).max);
121
+ }
122
+
123
+ /// @notice Counts credit accounts that match `caFilter` in a given `creditManager`
124
+ function countCreditAccounts(address creditManager, CreditAccountFilter memory caFilter)
125
+ external
126
+ view
127
+ returns (uint256)
128
+ {
129
+ address[] memory creditManagers = new address[](1);
130
+ creditManagers[0] = creditManager;
131
+ return _countCreditAccounts(creditManagers, caFilter, 0, type(uint256).max);
132
+ }
133
+
134
+ // --------- //
135
+ // INTERNALS //
136
+ // --------- //
137
+
138
+ /// @dev Querying implementation
139
+ function _getCreditAccounts(
140
+ address[] memory creditManagers,
141
+ CreditAccountFilter memory filter,
142
+ uint256 offset,
143
+ uint256 limit
144
+ ) internal view returns (CreditAccountData[] memory data, uint256 nextOffset) {
145
+ uint256 num = _countCreditAccounts(creditManagers, filter, offset, limit);
146
+ if (num == 0) return (data, 0);
147
+
148
+ // allocating the `CreditAccountData` array might consume most of the gas leaving no room for computations,
149
+ // so we instead allocate and gradually fill the array of pointers to structs which takes much less space
150
+ bytes32[] memory dataPointers = new bytes32[](num);
151
+ uint256 dataOffset;
152
+
153
+ // to adjust to RPC provider's call gas limit, the function stops when gas left gets below gas reserve, which
154
+ // starts at this number that should be enough to cover a single account processing, and increases with each
155
+ // new data struct to accommodate the cost of memory expansion that happens upon ABI-encoding returned data
156
+ uint256 gasReserve = 2e6;
157
+
158
+ nextOffset = offset;
159
+ for (uint256 i; i < creditManagers.length; ++i) {
160
+ address creditManager = creditManagers[i];
161
+ uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
162
+
163
+ // first, we need to get to the `offset` position
164
+ if (len <= offset) {
165
+ offset -= len;
166
+ continue;
167
+ }
168
+
169
+ uint256 count = Math.min(len - offset, limit);
170
+ address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, count);
171
+
172
+ // circumvent the "Stack too deep." error
173
+ CreditAccountFilter memory filter_ = filter;
174
+
175
+ for (uint256 j; j < creditAccounts.length; ++j) {
176
+ address creditAccount = creditAccounts[j];
177
+ if (_checkFilterMatch(creditAccount, creditManager, filter_)) {
178
+ uint256 gasBefore = gasleft();
179
+
180
+ CreditAccountData memory d = _getCreditAccountData(creditAccount, creditManager);
181
+ ++dataOffset;
182
+ assembly {
183
+ // save the pointer to created struct
184
+ mstore(add(dataPointers, mul(0x20, dataOffset)), d)
185
+ }
186
+
187
+ // rough approximation of gas that will be needed to accommodate additional memory expansion cost
188
+ gasReserve += (gasBefore - gasleft()) / 2;
189
+ }
190
+ --count;
191
+
192
+ if (dataOffset == num || gasleft() < gasReserve) break;
193
+ }
194
+
195
+ nextOffset += creditAccounts.length - count;
196
+ if (dataOffset == num || count != 0) break;
197
+
198
+ limit -= creditAccounts.length;
199
+ if (limit == 0) break;
200
+ offset = 0;
201
+ }
202
+
203
+ assembly {
204
+ // cast array of pointers to structs to array of structs
205
+ data := dataPointers
206
+ // trim array to its actual size
207
+ mstore(data, dataOffset)
208
+ }
209
+
210
+ // set `nextOffset` to zero to indicate that scanning is finished
211
+ if (dataOffset == num) nextOffset = 0;
212
+ }
213
+
214
+ /// @dev Counting implementation
215
+ function _countCreditAccounts(
216
+ address[] memory creditManagers,
217
+ CreditAccountFilter memory filter,
218
+ uint256 offset,
219
+ uint256 limit
220
+ ) internal view returns (uint256 num) {
221
+ for (uint256 i; i < creditManagers.length; ++i) {
222
+ address creditManager = creditManagers[i];
223
+ uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
224
+
225
+ // first, we need to get to the `offset` position
226
+ if (len <= offset) {
227
+ offset -= len;
228
+ continue;
229
+ }
230
+
231
+ address[] memory creditAccounts =
232
+ ICreditManagerV3(creditManager).creditAccounts(offset, Math.min(len - offset, limit));
233
+ for (uint256 j; j < creditAccounts.length; ++j) {
234
+ if (_checkFilterMatch(creditAccounts[j], creditManager, filter)) {
235
+ ++num;
236
+ }
237
+ }
238
+
239
+ limit -= creditAccounts.length;
240
+ if (limit == 0) break;
241
+ offset = 0;
242
+ }
243
+ }
244
+
245
+ /// @dev Data loading implementation
246
+ function _getCreditAccountData(address creditAccount, address creditManager)
247
+ internal
248
+ view
249
+ returns (CreditAccountData memory data)
250
+ {
251
+ data.creditAccount = creditAccount;
252
+ data.creditManager = creditManager;
253
+ data.creditFacade = ICreditManagerV3(creditManager).creditFacade();
254
+ data.underlying = ICreditManagerV3(creditManager).underlying();
255
+ data.owner = ICreditManagerV3(creditManager).getBorrowerOrRevert(creditAccount);
256
+
257
+ CollateralDebtData memory cdd =
258
+ ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_ONLY);
259
+ data.enabledTokensMask = cdd.enabledTokensMask;
260
+ data.debt = cdd.debt;
261
+ data.accruedInterest = cdd.accruedInterest;
262
+ data.accruedFees = cdd.accruedFees;
263
+
264
+ // collateral is computed separately since it might revert on `balanceOf` and `latestRoundData` calls
265
+ try ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL)
266
+ returns (CollateralDebtData memory cdd_) {
267
+ data.totalDebtUSD = cdd_.totalDebtUSD;
268
+ data.totalValueUSD = cdd_.totalValueUSD;
269
+ data.twvUSD = cdd_.twvUSD;
270
+ data.totalValue = cdd_.totalValue;
271
+
272
+ data.healthFactor = cdd_.twvUSD * PERCENTAGE_FACTOR >= type(uint16).max * cdd_.totalDebtUSD
273
+ ? type(uint16).max
274
+ : uint16(cdd_.twvUSD * PERCENTAGE_FACTOR / cdd_.totalDebtUSD);
275
+ data.success = true;
276
+ } catch {}
277
+
278
+ uint256 maxTokens = ICreditManagerV3(creditManager).collateralTokensCount();
279
+
280
+ // the function is called for every account, so allocating an array of size `maxTokens` and trimming it
281
+ // might cause issues with memory expansion and we must count the precise number of tokens in advance
282
+ uint256 numTokens;
283
+ uint256 returnedTokensMask;
284
+ for (uint256 k; k < maxTokens; ++k) {
285
+ uint256 mask = 1 << k;
286
+ if (cdd.enabledTokensMask & mask == 0) {
287
+ address token = ICreditManagerV3(creditManager).getTokenByMask(mask);
288
+ try IERC20(token).balanceOf(creditAccount) returns (uint256 balance) {
289
+ if (balance <= 1) continue;
290
+ } catch {
291
+ continue;
292
+ }
293
+ }
294
+ ++numTokens;
295
+ returnedTokensMask |= mask;
296
+ }
297
+
298
+ data.tokens = new TokenInfo[](numTokens);
299
+ uint256 i;
300
+ while (returnedTokensMask != 0) {
301
+ uint256 mask = returnedTokensMask & uint256(-int256(returnedTokensMask));
302
+ address token = ICreditManagerV3(creditManager).getTokenByMask(mask);
303
+ data.tokens[i].token = token;
304
+ data.tokens[i].mask = mask;
305
+
306
+ try IERC20(token).balanceOf(creditAccount) returns (uint256 balance) {
307
+ data.tokens[i].balance = balance;
308
+ data.tokens[i].success = true;
309
+ } catch {}
310
+
311
+ if (IPoolQuotaKeeperV3(cdd._poolQuotaKeeper).isQuotedToken(token)) {
312
+ (data.tokens[i].quota,) =
313
+ IPoolQuotaKeeperV3(cdd._poolQuotaKeeper).getQuotaAndOutstandingInterest(creditAccount, token);
314
+ }
315
+
316
+ returnedTokensMask ^= mask;
317
+ ++i;
318
+ }
319
+ }
320
+
321
+ /// @dev Credit account filtering implementation
322
+ function _checkFilterMatch(address creditAccount, address creditManager, CreditAccountFilter memory filter)
323
+ internal
324
+ view
325
+ returns (bool)
326
+ {
327
+ if (filter.owner != address(0)) {
328
+ address owner = ICreditManagerV3(creditManager).getBorrowerOrRevert(creditAccount);
329
+ if (owner != filter.owner) return false;
330
+ }
331
+
332
+ if (!filter.includeZeroDebt) {
333
+ (uint256 debt,,,,,,,) = ICreditManagerV3(creditManager).creditAccountInfo(creditAccount);
334
+ if (debt == 0) return false;
335
+ }
336
+
337
+ if (filter.minHealthFactor != 0 || filter.maxHealthFactor != 0 || filter.reverting) {
338
+ try ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL)
339
+ returns (CollateralDebtData memory cdd) {
340
+ if (filter.reverting) return false;
341
+ uint16 healthFactor = cdd.twvUSD * PERCENTAGE_FACTOR >= type(uint16).max * cdd.totalDebtUSD
342
+ ? type(uint16).max
343
+ : uint16(cdd.twvUSD * PERCENTAGE_FACTOR / cdd.totalDebtUSD);
344
+ if (filter.minHealthFactor != 0 && healthFactor < filter.minHealthFactor) return false;
345
+ if (filter.maxHealthFactor != 0 && healthFactor > filter.maxHealthFactor) return false;
346
+ } catch {
347
+ if (!filter.reverting) return false;
348
+ }
349
+ }
350
+
351
+ return true;
352
+ }
353
+
354
+ /// @dev Credit managers discovery
355
+ function _getCreditManagers(CreditManagerFilter memory filter)
356
+ internal
357
+ view
358
+ returns (address[] memory creditManagers)
359
+ {
360
+ address[] memory configurators = IAddressProviderV3(ADDRESS_PROVIDER).marketConfigurators();
361
+
362
+ // rough estimate of maximum number of credit managers
363
+ uint256 max;
364
+ for (uint256 i; i < configurators.length; ++i) {
365
+ address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
366
+ max += IContractsRegister(cr).getCreditManagers().length;
367
+ }
368
+
369
+ // allocate the array with maximum potentially needed size (total number of credit managers can be assumed
370
+ // to be relatively small and the function is only called once, so memary expansion cost is not an issue)
371
+ creditManagers = new address[](max);
372
+ uint256 num;
373
+ for (uint256 i; i < configurators.length; ++i) {
374
+ if (filter.curator != address(0)) {
375
+ if (IMarketConfiguratorV3(configurators[i]).owner() != filter.curator) continue;
376
+ }
377
+
378
+ address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
379
+ address[] memory managers = IContractsRegister(cr).getCreditManagers();
380
+ for (uint256 j; j < managers.length; ++j) {
381
+ // we're only concerned with v3 contracts
382
+ uint256 ver = IVersion(managers[j]).version();
383
+ if (ver < 3_00 || ver > 3_99) continue;
384
+
385
+ if (filter.pool != address(0)) {
386
+ if (ICreditManagerV3(managers[j]).pool() != filter.pool) continue;
387
+ }
388
+
389
+ if (filter.underlying != address(0)) {
390
+ if (ICreditManagerV3(managers[j]).underlying() != filter.underlying) continue;
391
+ }
392
+
393
+ creditManagers[num++] = managers[j];
394
+ }
395
+ }
396
+
397
+ // trim the array to its actual size
398
+ assembly {
399
+ mstore(creditManagers, num)
400
+ }
401
+ }
402
+ }
@@ -0,0 +1,224 @@
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 {AddressIsNotContractException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";
7
+ import {IPriceOracleV3, PriceFeedParams} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol";
8
+ import {IPriceFeed, IUpdatablePriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol";
9
+ import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
10
+ import {PriceFeedType} from "@gearbox-protocol/sdk-gov/contracts/PriceFeedType.sol";
11
+
12
+ import {IStateSerializerLegacy} from "../interfaces/IStateSerializerLegacy.sol";
13
+ import {IStateSerializer} from "../interfaces/IStateSerializer.sol";
14
+ import {NestedPriceFeeds} from "../libraries/NestedPriceFeeds.sol";
15
+ import {BoundedPriceFeedSerializer} from "../serializers/oracles/BoundedPriceFeedSerializer.sol";
16
+ import {BPTWeightedPriceFeedSerializer} from "../serializers/oracles/BPTWeightedPriceFeedSerializer.sol";
17
+ import {LPPriceFeedSerializer} from "../serializers/oracles/LPPriceFeedSerializer.sol";
18
+ import {PythPriceFeedSerializer} from "../serializers/oracles/PythPriceFeedSerializer.sol";
19
+ import {RedstonePriceFeedSerializer} from "../serializers/oracles/RedstonePriceFeedSerializer.sol";
20
+ import {PriceFeedAnswer, PriceFeedMapEntry, PriceFeedTreeNode} from "./Types.sol";
21
+
22
+ interface ImplementsPriceFeedType {
23
+ /// @dev Annotates `priceFeedType` as `uint8` instead of `PriceFeedType` enum to support future types
24
+ function priceFeedType() external view returns (uint8);
25
+ }
26
+
27
+ /// @dev Price oracle with version below `3_10` has some important interface differences:
28
+ /// - it does not implement `getTokens`
29
+ /// - it only allows to fetch staleness period of a currently active price feed
30
+ interface IPriceOracleV3Legacy {
31
+ /// @dev Older signature for fetching main and reserve feeds, reverts if price feed is not set
32
+ function priceFeedsRaw(address token, bool reserve) external view returns (address);
33
+ }
34
+
35
+ /// @title Price feed compressor
36
+ /// @notice Allows to fetch all useful data from price oracle in a single call
37
+ /// @dev The contract is not gas optimized and is thus not recommended for on-chain use
38
+ contract PriceFeedCompressor is IVersion {
39
+ using NestedPriceFeeds for IPriceFeed;
40
+
41
+ /// @notice Contract version
42
+ uint256 public constant override version = 3_10;
43
+
44
+ /// @notice Map of state serializers for different price feed types
45
+ /// @dev Serializers only apply to feeds that don't implement `IStateSerializer` themselves
46
+ mapping(uint8 => address) public serializers;
47
+
48
+ /// @notice Emitted when new state serializer is set for a given price feed type
49
+ event SetSerializer(uint8 indexed priceFeedType, address indexed serializer);
50
+
51
+ /// @notice Constructor
52
+ /// @dev Sets serializers for existing price feed types.
53
+ /// It is recommended to implement `IStateSerializer` in new price feeds.
54
+ constructor() {
55
+ address lpSerializer = address(new LPPriceFeedSerializer());
56
+ // these types can be serialized as generic LP price feeds
57
+ _setSerializer(uint8(PriceFeedType.BALANCER_STABLE_LP_ORACLE), lpSerializer);
58
+ _setSerializer(uint8(PriceFeedType.COMPOUND_V2_ORACLE), lpSerializer);
59
+ _setSerializer(uint8(PriceFeedType.CURVE_2LP_ORACLE), lpSerializer);
60
+ _setSerializer(uint8(PriceFeedType.CURVE_3LP_ORACLE), lpSerializer);
61
+ _setSerializer(uint8(PriceFeedType.CURVE_4LP_ORACLE), lpSerializer);
62
+ _setSerializer(uint8(PriceFeedType.CURVE_CRYPTO_ORACLE), lpSerializer);
63
+ _setSerializer(uint8(PriceFeedType.CURVE_USD_ORACLE), lpSerializer);
64
+ _setSerializer(uint8(PriceFeedType.ERC4626_VAULT_ORACLE), lpSerializer);
65
+ _setSerializer(uint8(PriceFeedType.WRAPPED_AAVE_V2_ORACLE), lpSerializer);
66
+ _setSerializer(uint8(PriceFeedType.WSTETH_ORACLE), lpSerializer);
67
+ _setSerializer(uint8(PriceFeedType.YEARN_ORACLE), lpSerializer);
68
+
69
+ // these types need special serialization
70
+ _setSerializer(uint8(PriceFeedType.BALANCER_WEIGHTED_LP_ORACLE), address(new BPTWeightedPriceFeedSerializer()));
71
+ _setSerializer(uint8(PriceFeedType.BOUNDED_ORACLE), address(new BoundedPriceFeedSerializer()));
72
+ _setSerializer(uint8(PriceFeedType.PYTH_ORACLE), address(new PythPriceFeedSerializer()));
73
+ _setSerializer(uint8(PriceFeedType.REDSTONE_ORACLE), address(new RedstonePriceFeedSerializer()));
74
+ }
75
+
76
+ /// @notice Returns all potentially useful price feeds data for a given price oracle in the form of two arrays:
77
+ /// - `priceFeedMap` is a set of entries in the map (token, reserve) => (priceFeed, stalenessPeirod).
78
+ /// These are all the price feeds one can actually query via the price oracle.
79
+ /// - `priceFeedTree` is a set of nodes in a tree-like structure that contains detailed info of both feeds
80
+ /// from `priceFeedMap` and their underlying feeds, in case former are nested, which can help to determine
81
+ /// what underlying feeds should be updated to query the nested one.
82
+ function getPriceFeeds(address priceOracle)
83
+ external
84
+ view
85
+ returns (PriceFeedMapEntry[] memory priceFeedMap, PriceFeedTreeNode[] memory priceFeedTree)
86
+ {
87
+ address[] memory tokens = IPriceOracleV3(priceOracle).getTokens();
88
+ return getPriceFeeds(priceOracle, tokens);
89
+ }
90
+
91
+ /// @dev Same as the above but takes the list of tokens as argument as legacy oracle doesn't implement `getTokens`
92
+ function getPriceFeeds(address priceOracle, address[] memory tokens)
93
+ public
94
+ view
95
+ returns (PriceFeedMapEntry[] memory priceFeedMap, PriceFeedTreeNode[] memory priceFeedTree)
96
+ {
97
+ uint256 numTokens = tokens.length;
98
+
99
+ priceFeedMap = new PriceFeedMapEntry[](2 * numTokens);
100
+ uint256 priceFeedMapSize;
101
+ uint256 priceFeedTreeSize;
102
+
103
+ for (uint256 i; i < 2 * numTokens; ++i) {
104
+ address token = tokens[i % numTokens];
105
+ bool reserve = i >= numTokens;
106
+
107
+ (address priceFeed, uint32 stalenessPeriod) = _getPriceFeed(priceOracle, token, reserve);
108
+ if (priceFeed == address(0)) continue;
109
+
110
+ priceFeedMap[priceFeedMapSize++] = PriceFeedMapEntry({
111
+ token: token,
112
+ reserve: reserve,
113
+ priceFeed: priceFeed,
114
+ stalenessPeriod: stalenessPeriod
115
+ });
116
+ priceFeedTreeSize += _getPriceFeedTreeSize(priceFeed);
117
+ }
118
+ assembly {
119
+ mstore(priceFeedMap, priceFeedMapSize)
120
+ }
121
+
122
+ priceFeedTree = new PriceFeedTreeNode[](priceFeedTreeSize);
123
+ uint256 offset;
124
+ for (uint256 i; i < priceFeedMapSize; ++i) {
125
+ offset = _loadPriceFeedTree(priceFeedMap[i].priceFeed, priceFeedTree, offset);
126
+ }
127
+ // trim array to its actual size in case there were duplicates
128
+ assembly {
129
+ mstore(priceFeedTree, offset)
130
+ }
131
+ }
132
+
133
+ // --------- //
134
+ // INTERNALS //
135
+ // --------- //
136
+
137
+ /// @dev Sets `serializer` for `priceFeedType`
138
+ function _setSerializer(uint8 priceFeedType, address serializer) internal {
139
+ if (serializers[priceFeedType] != serializer) {
140
+ serializers[priceFeedType] = serializer;
141
+ emit SetSerializer(priceFeedType, serializer);
142
+ }
143
+ }
144
+
145
+ /// @dev Returns `token`'s price feed in the price oracle
146
+ function _getPriceFeed(address priceOracle, address token, bool reserve) internal view returns (address, uint32) {
147
+ if (IPriceOracleV3(priceOracle).version() < 3_10) {
148
+ try IPriceOracleV3Legacy(priceOracle).priceFeedsRaw(token, reserve) returns (address priceFeed) {
149
+ // legacy oracle does not allow to fetch staleness period of a non-active feed
150
+ return (priceFeed, 0);
151
+ } catch {
152
+ return (address(0), 0);
153
+ }
154
+ }
155
+ PriceFeedParams memory params = reserve
156
+ ? IPriceOracleV3(priceOracle).reservePriceFeedParams(token)
157
+ : IPriceOracleV3(priceOracle).priceFeedParams(token);
158
+ return (params.priceFeed, params.stalenessPeriod);
159
+ }
160
+
161
+ /// @dev Computes the size of the `priceFeed`'s subtree (recursively)
162
+ function _getPriceFeedTreeSize(address priceFeed) internal view returns (uint256 size) {
163
+ size = 1;
164
+ (address[] memory underlyingFeeds,) = IPriceFeed(priceFeed).getUnderlyingFeeds();
165
+ for (uint256 i; i < underlyingFeeds.length; ++i) {
166
+ size += _getPriceFeedTreeSize(underlyingFeeds[i]);
167
+ }
168
+ }
169
+
170
+ /// @dev Loads `priceFeed`'s subtree (recursively)
171
+ function _loadPriceFeedTree(address priceFeed, PriceFeedTreeNode[] memory priceFeedTree, uint256 offset)
172
+ internal
173
+ view
174
+ returns (uint256)
175
+ {
176
+ // duplicates are possible since price feed can be in `priceFeedMap` for more than one (token, reserve) pair
177
+ // or serve as an underlying in more than one nested feed, and the whole subtree can be skipped in this case
178
+ for (uint256 i; i < offset; ++i) {
179
+ if (priceFeedTree[i].priceFeed == priceFeed) return offset;
180
+ }
181
+
182
+ PriceFeedTreeNode memory node = _getPriceFeedTreeNode(priceFeed);
183
+ priceFeedTree[offset++] = node;
184
+ for (uint256 i; i < node.underlyingFeeds.length; ++i) {
185
+ offset = _loadPriceFeedTree(node.underlyingFeeds[i], priceFeedTree, offset);
186
+ }
187
+ return offset;
188
+ }
189
+
190
+ /// @dev Returns price feed tree node, see `PriceFeedTreeNode` for detailed description of struct fields
191
+ function _getPriceFeedTreeNode(address priceFeed) internal view returns (PriceFeedTreeNode memory data) {
192
+ data.priceFeed = priceFeed;
193
+ data.decimals = IPriceFeed(priceFeed).decimals();
194
+
195
+ try ImplementsPriceFeedType(priceFeed).priceFeedType() returns (uint8 priceFeedType) {
196
+ data.priceFeedType = priceFeedType;
197
+ } catch {
198
+ data.priceFeedType = uint8(PriceFeedType.CHAINLINK_ORACLE);
199
+ }
200
+
201
+ try IPriceFeed(priceFeed).skipPriceCheck() returns (bool skipCheck) {
202
+ data.skipCheck = skipCheck;
203
+ } catch {}
204
+
205
+ try IUpdatablePriceFeed(priceFeed).updatable() returns (bool updatable) {
206
+ data.updatable = updatable;
207
+ } catch {}
208
+
209
+ try IStateSerializer(priceFeed).serialize() returns (bytes memory specificParams) {
210
+ data.specificParams = specificParams;
211
+ } catch {
212
+ address serializer = serializers[data.priceFeedType];
213
+ if (serializer != address(0)) {
214
+ data.specificParams = IStateSerializerLegacy(serializer).serialize(priceFeed);
215
+ }
216
+ }
217
+
218
+ (data.underlyingFeeds, data.underlyingStalenessPeriods) = IPriceFeed(priceFeed).getUnderlyingFeeds();
219
+
220
+ try IPriceFeed(priceFeed).latestRoundData() returns (uint80, int256 price, uint256, uint256 updatedAt, uint80) {
221
+ data.answer = PriceFeedAnswer({price: price, updatedAt: updatedAt, success: true});
222
+ } catch {}
223
+ }
224
+ }
@@ -0,0 +1,120 @@
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's 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`, in bps
21
+ /// @param success Whether collateral calculation was successful
22
+ /// @param tokens Info on credit account's enabled tokens and tokens with non-zero balance, 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
+ uint16 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
+ uint16 minHealthFactor;
54
+ uint16 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
+ /// `IStateSerializer` 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 mask Token mask in the credit manager
111
+ /// @param balance Account's balance of token
112
+ /// @param quota Account's quota of token
113
+ /// @param success Whether balance call was successful
114
+ struct TokenInfo {
115
+ address token;
116
+ uint256 mask;
117
+ uint256 balance;
118
+ uint256 quota;
119
+ bool success;
120
+ }
@@ -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 IStateSerializer {
9
+ function serialize() 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
7
+ /// @notice Generic interface of a contract that is able to serialize state of other contracts
8
+ interface IStateSerializerLegacy {
9
+ function serialize(address) 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 {IStateSerializerLegacy} from "../../interfaces/IStateSerializerLegacy.sol";
8
+
9
+ contract BoundedPriceFeedSerializer is IStateSerializerLegacy {
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 {IStateSerializerLegacy} from "../../interfaces/IStateSerializerLegacy.sol";
8
+
9
+ contract LPPriceFeedSerializer is IStateSerializerLegacy {
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 {IStateSerializerLegacy} from "../../interfaces/IStateSerializerLegacy.sol";
8
+
9
+ contract PythPriceFeedSerializer is IStateSerializerLegacy {
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 {IStateSerializerLegacy} from "../../interfaces/IStateSerializerLegacy.sol";
8
+
9
+ contract RedstonePriceFeedSerializer is IStateSerializerLegacy {
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.11",
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
  },