@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.
- package/contracts/compressors/CreditAccountCompressor.sol +402 -0
- package/contracts/compressors/PriceFeedCompressor.sol +224 -0
- package/contracts/compressors/Types.sol +120 -0
- package/contracts/interfaces/IStateSerializer.sol +10 -0
- package/contracts/interfaces/IStateSerializerLegacy.sol +10 -0
- package/contracts/libraries/NestedPriceFeeds.sol +153 -0
- package/contracts/serializers/oracles/BPTWeightedPriceFeedSerializer.sol +26 -0
- package/contracts/serializers/oracles/BoundedPriceFeedSerializer.sol +15 -0
- package/contracts/serializers/oracles/LPPriceFeedSerializer.sol +46 -0
- package/contracts/serializers/oracles/PythPriceFeedSerializer.sol +15 -0
- package/contracts/serializers/oracles/RedstonePriceFeedSerializer.sol +35 -0
- package/package.json +6 -3
|
@@ -0,0 +1,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.
|
|
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.
|
|
21
|
+
"@gearbox-protocol/core-v3": "^1.50.0-next.15",
|
|
22
|
+
"@gearbox-protocol/governance": "^1.4.0-next.5",
|
|
21
23
|
"@gearbox-protocol/integrations-v3": "^1.23.1",
|
|
22
|
-
"@gearbox-protocol/oracles-v3": "^1.11.0-next.
|
|
24
|
+
"@gearbox-protocol/oracles-v3": "^1.11.0-next.3",
|
|
23
25
|
"@gearbox-protocol/sdk-gov": "^2.3.1",
|
|
24
26
|
"@openzeppelin/contracts": "^4.9.3",
|
|
27
|
+
"@redstone-finance/evm-connector": "0.2.5",
|
|
25
28
|
"ds-test": "dapphub/ds-test",
|
|
26
29
|
"forge-std": "foundry-rs/forge-std"
|
|
27
30
|
},
|