@gearbox-protocol/periphery-v3 1.7.0-next.6 → 1.7.0-next.60

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.
Files changed (60) hide show
  1. package/contracts/compressors/AdapterCompressor.sol +86 -0
  2. package/contracts/compressors/CreditAccountCompressor.sol +218 -208
  3. package/contracts/compressors/CreditSuiteCompressor.sol +99 -0
  4. package/contracts/compressors/GaugeCompressor.sol +200 -0
  5. package/contracts/compressors/MarketCompressor.sol +223 -0
  6. package/contracts/compressors/PeripheryCompressor.sol +115 -0
  7. package/contracts/compressors/PoolCompressor.sol +207 -0
  8. package/contracts/compressors/PriceFeedCompressor.sol +104 -58
  9. package/contracts/compressors/RewardsCompressor.sol +279 -0
  10. package/contracts/compressors/TokenCompressor.sol +51 -0
  11. package/contracts/data/DataCompressorV3.sol +9 -8
  12. package/contracts/data/Types.sol +1 -1
  13. package/contracts/data/ZapperRegister.sol +7 -8
  14. package/contracts/emergency/MultiPause.sol +19 -11
  15. package/contracts/interfaces/IAdapterCompressor.sol +12 -0
  16. package/contracts/interfaces/ICreditAccountCompressor.sol +61 -0
  17. package/contracts/interfaces/ICreditSuiteCompressor.sol +18 -0
  18. package/contracts/interfaces/IGaugeCompressor.sol +14 -0
  19. package/contracts/interfaces/IMarketCompressor.sol +22 -0
  20. package/contracts/interfaces/IPeripheryCompressor.sol +16 -0
  21. package/contracts/interfaces/IPoolCompressor.sol +23 -0
  22. package/contracts/interfaces/IPriceFeedCompressor.sol +28 -0
  23. package/contracts/interfaces/IRewardsCompressor.sol +80 -0
  24. package/contracts/interfaces/{IStateSerializer.sol → IStateSerializerLegacy.sol} +1 -1
  25. package/contracts/interfaces/ITokenCompressor.sol +13 -0
  26. package/contracts/libraries/BaseLib.sol +42 -0
  27. package/contracts/libraries/Contains.sol +15 -0
  28. package/contracts/libraries/Literals.sol +15 -0
  29. package/contracts/serializers/oracles/BoundedPriceFeedSerializer.sol +2 -2
  30. package/contracts/serializers/oracles/LPPriceFeedSerializer.sol +4 -4
  31. package/contracts/serializers/oracles/PendleTWAPPTPriceFeedSerializer.sol +24 -0
  32. package/contracts/serializers/oracles/PythPriceFeedSerializer.sol +2 -2
  33. package/contracts/serializers/oracles/RedstonePriceFeedSerializer.sol +2 -2
  34. package/contracts/serializers/pool/GaugeSerializer.sol +32 -0
  35. package/contracts/serializers/pool/LinearInterestRateModelSerializer.sol +26 -0
  36. package/contracts/test/ForkTest.sol +33 -11
  37. package/contracts/test/MultiPause.t.sol +14 -20
  38. package/contracts/test/equivalence/CreditAccountsEquivalenceTest.t.sol +197 -0
  39. package/contracts/test/equivalence/CreditSuiteEquivalenceTest.t.sol +198 -0
  40. package/contracts/test/equivalence/GaugeEquivalenceTest.t.sol +96 -0
  41. package/contracts/test/equivalence/PoolsEquivalenceTest.t.sol +149 -0
  42. package/contracts/test/interfaces/IACLExt.sol +1 -1
  43. package/contracts/test/interfaces/IContractsRegisterExt.sol +1 -1
  44. package/contracts/types/BaseState.sol +15 -0
  45. package/contracts/types/CreditAccountState.sol +58 -0
  46. package/contracts/types/CreditFacadeState.sol +20 -0
  47. package/contracts/types/CreditManagerState.sol +28 -0
  48. package/contracts/types/CreditSuiteData.sol +20 -0
  49. package/contracts/types/Filters.sol +30 -0
  50. package/contracts/types/MarketData.sol +32 -0
  51. package/contracts/types/PeripheryState.sol +19 -0
  52. package/contracts/types/PoolQuotaKeeperState.sol +24 -0
  53. package/contracts/types/PoolState.sol +49 -0
  54. package/contracts/types/PriceOracleState.sol +58 -0
  55. package/contracts/types/RateKeeperState.sol +39 -0
  56. package/contracts/types/TokenData.sol +11 -0
  57. package/package.json +12 -12
  58. package/contracts/compressors/Types.sol +0 -132
  59. package/contracts/data/LinearInterestModelHelper.sol +0 -19
  60. package/contracts/interfaces/IStateSerializerTrait.sol +0 -10
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Gearbox Protocol. Generalized leverage for DeFi protocols
3
+ // (c) Gearbox Holdings, 2024
4
+ pragma solidity ^0.8.17;
5
+
6
+ import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
7
+ import {IStateSerializer} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IStateSerializer.sol";
8
+ import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol";
9
+ import {AdapterState} from "../types/CreditSuiteData.sol";
10
+ import {IAdapterCompressor} from "../interfaces/IAdapterCompressor.sol";
11
+ import {ICreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditConfiguratorV3.sol";
12
+ import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
13
+ import {AP_ADAPTER_COMPRESSOR} from "../libraries/Literals.sol";
14
+
15
+ interface ILegacyAdapter {
16
+ function _gearboxAdapterVersion() external view returns (uint16);
17
+ function _gearboxAdapterType() external view returns (uint8);
18
+ }
19
+
20
+ contract AdapterCompressor is IAdapterCompressor {
21
+ uint256 public constant override version = 3_10;
22
+ bytes32 public constant override contractType = AP_ADAPTER_COMPRESSOR;
23
+
24
+ mapping(uint8 => bytes32) public contractTypes;
25
+
26
+ constructor() {
27
+ contractTypes[uint8(AdapterType.BALANCER_VAULT)] = "ADAPTER::BALANCER_VAULT";
28
+ contractTypes[uint8(AdapterType.BALANCER_V3_ROUTER)] = "ADAPTER::BALANCER_V3_ROUTER";
29
+ contractTypes[uint8(AdapterType.CAMELOT_V3_ROUTER)] = "ADAPTER::CAMELOT_V3_ROUTER";
30
+ contractTypes[uint8(AdapterType.CONVEX_V1_BASE_REWARD_POOL)] = "ADAPTER::CVX_V1_BASE_REWARD_POOL";
31
+ contractTypes[uint8(AdapterType.CONVEX_V1_BOOSTER)] = "ADAPTER::CVX_V1_BOOSTER";
32
+ contractTypes[uint8(AdapterType.CURVE_V1_2ASSETS)] = "ADAPTER::CURVE_V1_2ASSETS";
33
+ contractTypes[uint8(AdapterType.CURVE_V1_3ASSETS)] = "ADAPTER::CURVE_V1_3ASSETS";
34
+ contractTypes[uint8(AdapterType.CURVE_V1_4ASSETS)] = "ADAPTER::CURVE_V1_4ASSETS";
35
+ contractTypes[uint8(AdapterType.CURVE_STABLE_NG)] = "ADAPTER::CURVE_STABLE_NG";
36
+ contractTypes[uint8(AdapterType.CURVE_V1_STECRV_POOL)] = "ADAPTER::CURVE_V1_STECRV_POOL";
37
+ contractTypes[uint8(AdapterType.CURVE_V1_WRAPPER)] = "ADAPTER::CURVE_V1_WRAPPER";
38
+ contractTypes[uint8(AdapterType.DAI_USDS_EXCHANGE)] = "ADAPTER::DAI_USDS_EXCHANGE";
39
+ contractTypes[uint8(AdapterType.EQUALIZER_ROUTER)] = "ADAPTER::EQUALIZER_ROUTER";
40
+ contractTypes[uint8(AdapterType.ERC4626_VAULT)] = "ADAPTER::ERC4626_VAULT";
41
+ contractTypes[uint8(AdapterType.LIDO_V1)] = "ADAPTER::LIDO_V1";
42
+ contractTypes[uint8(AdapterType.LIDO_WSTETH_V1)] = "ADAPTER::LIDO_WSTETH_V1";
43
+ contractTypes[uint8(AdapterType.MELLOW_ERC4626_VAULT)] = "ADAPTER::MELLOW_ERC4626_VAULT";
44
+ contractTypes[uint8(AdapterType.MELLOW_LRT_VAULT)] = "ADAPTER::MELLOW_LRT_VAULT";
45
+ contractTypes[uint8(AdapterType.PENDLE_ROUTER)] = "ADAPTER::PENDLE_ROUTER";
46
+ contractTypes[uint8(AdapterType.STAKING_REWARDS)] = "ADAPTER::STAKING_REWARDS";
47
+ contractTypes[uint8(AdapterType.UNISWAP_V2_ROUTER)] = "ADAPTER::UNISWAP_V2_ROUTER";
48
+ contractTypes[uint8(AdapterType.UNISWAP_V3_ROUTER)] = "ADAPTER::UNISWAP_V3_ROUTER";
49
+ contractTypes[uint8(AdapterType.VELODROME_V2_ROUTER)] = "ADAPTER::VELODROME_V2_ROUTER";
50
+ contractTypes[uint8(AdapterType.YEARN_V2)] = "ADAPTER::YEARN_V2";
51
+ contractTypes[uint8(AdapterType.ZIRCUIT_POOL)] = "ADAPTER::ZIRCUIT_POOL";
52
+ }
53
+
54
+ function getAdapters(address creditManager) external view returns (AdapterState[] memory adapters) {
55
+ ICreditConfiguratorV3 creditConfigurator =
56
+ ICreditConfiguratorV3(ICreditManagerV3(creditManager).creditConfigurator());
57
+
58
+ address[] memory allowedAdapters = creditConfigurator.allowedAdapters();
59
+ uint256 len = allowedAdapters.length;
60
+
61
+ adapters = new AdapterState[](len);
62
+ unchecked {
63
+ for (uint256 i = 0; i < len; ++i) {
64
+ address adapter = allowedAdapters[i];
65
+ adapters[i].baseParams.addr = adapter;
66
+ try IVersion(adapter).contractType() returns (bytes32 contractType_) {
67
+ adapters[i].baseParams.contractType = contractType_;
68
+ } catch {
69
+ adapters[i].baseParams.contractType = contractTypes[ILegacyAdapter(adapter)._gearboxAdapterType()];
70
+ }
71
+
72
+ try IVersion(adapter).version() returns (uint256 v) {
73
+ adapters[i].baseParams.version = v;
74
+ } catch {
75
+ adapters[i].baseParams.version = ILegacyAdapter(adapter)._gearboxAdapterVersion();
76
+ }
77
+
78
+ try IStateSerializer(adapter).serialize() returns (bytes memory serializedParams) {
79
+ adapters[i].baseParams.serializedParams = serializedParams;
80
+ } catch {}
81
+
82
+ adapters[i].targetContract = ICreditManagerV3(creditManager).adapterToContract(adapter);
83
+ }
84
+ }
85
+ }
86
+ }
@@ -3,55 +3,67 @@
3
3
  // (c) Gearbox Foundation, 2024.
4
4
  pragma solidity ^0.8.17;
5
5
 
6
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
6
7
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
8
  import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
8
9
 
9
- import {IContractsRegister} from "@gearbox-protocol/core-v3/contracts/interfaces/IContractsRegister.sol";
10
10
  import {ICreditAccountV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditAccountV3.sol";
11
+ import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
11
12
  import {
12
13
  CollateralCalcTask,
13
14
  CollateralDebtData,
14
15
  ICreditManagerV3
15
16
  } from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
16
17
  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
18
  import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
19
19
  import {SanityCheckTrait} from "@gearbox-protocol/core-v3/contracts/traits/SanityCheckTrait.sol";
20
+ import {ICreditAccountCompressor} from "../interfaces/ICreditAccountCompressor.sol";
20
21
 
21
- import {IAddressProviderV3} from "@gearbox-protocol/governance/contracts/interfaces/IAddressProviderV3.sol";
22
- import {IMarketConfiguratorV3} from "@gearbox-protocol/governance/contracts/interfaces/IMarketConfiguratorV3.sol";
22
+ import {IContractsRegister} from "@gearbox-protocol/governance/contracts/interfaces/IContractsRegister.sol";
23
+ import {IAddressProvider} from "@gearbox-protocol/governance/contracts/interfaces/IAddressProvider.sol";
24
+ import {IMarketConfigurator} from "@gearbox-protocol/governance/contracts/interfaces/IMarketConfigurator.sol";
25
+ import {IMarketConfiguratorFactory} from
26
+ "@gearbox-protocol/governance/contracts/interfaces/IMarketConfiguratorFactory.sol";
27
+ import {
28
+ AP_MARKET_CONFIGURATOR_FACTORY,
29
+ NO_VERSION_CONTROL
30
+ } from "@gearbox-protocol/governance/contracts/libraries/ContractLiterals.sol";
23
31
 
24
- import {CreditAccountData, CreditAccountFilter, CreditManagerFilter, Pagination, TokenInfo} from "./Types.sol";
32
+ import {CreditAccountData, TokenInfo} from "../types/CreditAccountState.sol";
33
+ import {CreditAccountFilter, MarketFilter} from "../types/Filters.sol";
25
34
 
35
+ import {Contains} from "../libraries/Contains.sol";
36
+ import {AP_CREDIT_ACCOUNT_COMPRESSOR} from "../libraries/Literals.sol";
26
37
  /// @title Credit account compressor
27
38
  /// @notice Allows to fetch data on all credit accounts matching certain criteria in an efficient manner
28
39
  /// @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
- /// but additional pagination options are available, see `Pagination`
31
- contract CreditAccountCompressor is IVersion, SanityCheckTrait {
40
+ /// @dev Querying functions try to process as many accounts as possible and stop when they get close to gas limit
41
+
42
+ contract CreditAccountCompressor is ICreditAccountCompressor, SanityCheckTrait {
43
+ using Contains for address[];
44
+
32
45
  /// @notice Contract version
33
46
  uint256 public constant override version = 3_10;
47
+ bytes32 public constant override contractType = AP_CREDIT_ACCOUNT_COMPRESSOR;
34
48
 
35
49
  /// @notice Address provider contract address
36
- address public immutable ADDRESS_PROVIDER;
50
+ address public immutable addressProvider;
37
51
 
38
- /// @dev Amount of gas that covers filter matching and data preparation for a single account
39
- /// @dev After processing a large number of accounts, gas costs might eventually exceed this number due to
40
- /// quadratic memory expansion cost and memory always growing in Solidity
41
- uint256 internal constant GAS_THRESHOLD = 2e6;
52
+ address public immutable marketConfiguratorFactory;
42
53
 
43
- /// @notice Thrown when address provider is not a contract or does not implement `marketConfigurators()`
54
+ /// @notice Thrown when address provider is not a contract
44
55
  error InvalidAddressProviderException();
45
56
 
46
57
  /// @notice Constructor
47
- /// @param addressProvider Address provider contract address
48
- constructor(address addressProvider) nonZeroAddress(addressProvider) {
49
- if (addressProvider.code.length == 0) revert InvalidAddressProviderException();
50
- try IAddressProviderV3(addressProvider).marketConfigurators() {}
51
- catch {
58
+ /// @param addressProvider_ Address provider contract address
59
+ constructor(address addressProvider_) nonZeroAddress(addressProvider_) {
60
+ if (addressProvider_.code.length == 0) {
52
61
  revert InvalidAddressProviderException();
53
62
  }
54
- ADDRESS_PROVIDER = addressProvider;
63
+
64
+ addressProvider = addressProvider_;
65
+ marketConfiguratorFactory =
66
+ IAddressProvider(addressProvider_).getAddressOrRevert(AP_MARKET_CONFIGURATOR_FACTORY, NO_VERSION_CONTROL);
55
67
  }
56
68
 
57
69
  // -------- //
@@ -64,163 +76,77 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
64
76
  return _getCreditAccountData(creditAccount, creditManager);
65
77
  }
66
78
 
67
- /// @notice Returns data for credit accounts that match `caFilter` in credit managers matching `cmFilter`
68
- /// @dev The `false` value of `finished` return variable indicates either that return limit was reached or
69
- /// that gas supplied with a call was insufficient to process all the accounts and next iteration is
70
- /// needed which should start from the `nextOffset` position
79
+ /// @notice Returns data for credit accounts that match `caFilter` in credit managers matching `marketFilter`
80
+ /// @dev The non-zero value of `nextOffset` return variable indicates that gas supplied with a call was
81
+ /// insufficient to process all the accounts and next iteration starting from this value is needed
82
+ function getCreditAccounts(MarketFilter memory marketFilter, CreditAccountFilter memory caFilter, uint256 offset)
83
+ external
84
+ view
85
+ returns (CreditAccountData[] memory data, uint256 nextOffset)
86
+ {
87
+ address[] memory creditManagers = _getCreditManagers(marketFilter);
88
+ return _getCreditAccounts(creditManagers, caFilter, offset, type(uint256).max);
89
+ }
90
+
91
+ /// @dev Same as above but with `limit` parameter that specifies the number of accounts to process
71
92
  function getCreditAccounts(
72
- CreditManagerFilter memory cmFilter,
93
+ MarketFilter memory marketFilter,
73
94
  CreditAccountFilter memory caFilter,
74
- Pagination memory pagination
75
- ) external view returns (CreditAccountData[] memory data, bool finished, uint256 nextOffset) {
76
- uint256 num = countCreditAccounts(cmFilter, caFilter, pagination);
77
- if (num == 0) return (data, true, 0);
78
-
79
- // create a second object to avoid modifying `pagination` which can have side-effects in the calling code
80
- Pagination memory _p = Pagination({
81
- offset: pagination.offset,
82
- scanLimit: pagination.scanLimit == 0 ? type(uint256).max : pagination.scanLimit,
83
- returnLimit: pagination.returnLimit == 0 ? type(uint256).max : pagination.returnLimit
84
- });
85
-
86
- data = new CreditAccountData[](Math.min(_p.returnLimit, num));
87
- uint256 dataOffset;
88
-
89
- nextOffset = _p.offset;
90
- address[] memory creditManagers = _getCreditManagers(cmFilter);
91
- for (uint256 i; i < creditManagers.length; ++i) {
92
- uint256 len = ICreditManagerV3(creditManagers[i]).creditAccountsLen();
93
-
94
- // first, we need to get to the `offset` position
95
- if (len <= _p.offset) {
96
- _p.offset -= len;
97
- continue;
98
- }
99
-
100
- uint256 limit = Math.min(len - _p.offset, _p.scanLimit);
101
-
102
- uint256 scanned;
103
- (dataOffset, scanned) = _getCreditAccounts({
104
- creditManager: creditManagers[i],
105
- filter: caFilter,
106
- data: data,
107
- dataOffset: dataOffset,
108
- offset: _p.offset,
109
- limit: limit
110
- });
111
- nextOffset += scanned;
112
-
113
- // either finished or reached one of return size or gas limits
114
- if (dataOffset == data.length || scanned < limit) break;
115
-
116
- _p.scanLimit -= limit;
117
- if (_p.scanLimit == 0) break;
118
- _p.offset = 0;
119
- }
120
-
121
- if (dataOffset < data.length) {
122
- // trim the array to its actual size
123
- assembly {
124
- mstore(data, dataOffset)
125
- }
126
- }
127
-
128
- return (data, dataOffset == num, nextOffset);
95
+ uint256 offset,
96
+ uint256 limit
97
+ ) public view returns (CreditAccountData[] memory data, uint256 nextOffset) {
98
+ address[] memory creditManagers = _getCreditManagers(marketFilter);
99
+ return _getCreditAccounts(creditManagers, caFilter, offset, limit);
129
100
  }
130
101
 
131
102
  /// @notice Returns data for credit accounts that match `caFilter` in a given `creditManager`
132
- /// @dev The `false` value of `finished` return variable indicates either that return limit was reached or
133
- /// that gas supplied with a call was insufficient to process all the accounts and next iteration is
134
- /// needed which should start from the `nextOffset` position
135
- function getCreditAccounts(address creditManager, CreditAccountFilter memory caFilter, Pagination memory pagination)
103
+ /// @dev The non-zero value of `nextOffset` return variable indicates that gas supplied with a call was
104
+ /// insufficient to process all the accounts and next iteration starting from this value is needed
105
+ function getCreditAccounts(address creditManager, CreditAccountFilter memory caFilter, uint256 offset)
136
106
  external
137
107
  view
138
- returns (CreditAccountData[] memory data, bool finished, uint256 nextOffset)
108
+ returns (CreditAccountData[] memory data, uint256 nextOffset)
139
109
  {
140
- uint256 num = countCreditAccounts(creditManager, caFilter, pagination);
141
- if (num == 0) return (data, true, 0);
142
-
143
- // create a second object to avoid modifying `pagination` which can have side-effects in the calling code
144
- Pagination memory _p = Pagination({
145
- offset: pagination.offset,
146
- scanLimit: pagination.scanLimit == 0 ? type(uint256).max : pagination.scanLimit,
147
- returnLimit: pagination.returnLimit == 0 ? type(uint256).max : pagination.returnLimit
148
- });
149
-
150
- data = new CreditAccountData[](Math.min(_p.returnLimit, num));
151
- uint256 dataOffset;
152
-
153
- uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
154
- // since `num != 0`, we can be sure that `len > offset`
155
- uint256 limit = Math.min(_p.scanLimit, len - _p.offset);
156
-
157
- uint256 scanned;
158
- (dataOffset, scanned) = _getCreditAccounts({
159
- creditManager: creditManager,
160
- filter: caFilter,
161
- data: data,
162
- dataOffset: 0,
163
- offset: _p.offset,
164
- limit: limit
165
- });
166
- nextOffset = _p.offset + scanned;
167
-
168
- if (dataOffset < data.length) {
169
- // trim the array to its actual size
170
- assembly {
171
- mstore(data, dataOffset)
172
- }
173
- }
110
+ address[] memory creditManagers = new address[](1);
111
+ creditManagers[0] = creditManager;
112
+ return _getCreditAccounts(creditManagers, caFilter, offset, type(uint256).max);
113
+ }
174
114
 
175
- return (data, dataOffset == num, nextOffset);
115
+ /// @dev Same as above but with `limit` parameter that specifies the number of accounts to process
116
+ function getCreditAccounts(
117
+ address creditManager,
118
+ CreditAccountFilter memory caFilter,
119
+ uint256 offset,
120
+ uint256 limit
121
+ ) external view returns (CreditAccountData[] memory data, uint256 nextOffset) {
122
+ address[] memory creditManagers = new address[](1);
123
+ creditManagers[0] = creditManager;
124
+ return _getCreditAccounts(creditManagers, caFilter, offset, limit);
176
125
  }
177
126
 
178
127
  // -------- //
179
128
  // COUNTING //
180
129
  // -------- //
181
130
 
182
- /// @notice Counts credit accounts that match `caFilter` in credit managers matching `cmFilter`
183
- function countCreditAccounts(
184
- CreditManagerFilter memory cmFilter,
185
- CreditAccountFilter memory caFilter,
186
- Pagination memory pagination
187
- ) public view returns (uint256 num) {
188
- uint256 offset = pagination.offset;
189
- uint256 scanLimit = pagination.scanLimit == 0 ? type(uint256).max : pagination.scanLimit;
190
-
191
- address[] memory creditManagers = _getCreditManagers(cmFilter);
192
- for (uint256 i; i < creditManagers.length; ++i) {
193
- uint256 len = ICreditManagerV3(creditManagers[i]).creditAccountsLen();
194
-
195
- // first, we need to get to the `offset` position
196
- if (len <= offset) {
197
- offset -= len;
198
- continue;
199
- }
200
-
201
- uint256 limit = Math.min(len - offset, scanLimit);
202
- num += _countCreditAccounts(creditManagers[i], caFilter, offset, limit);
203
-
204
- scanLimit -= limit;
205
- if (scanLimit == 0) break;
206
- offset = 0;
207
- }
131
+ /// @notice Counts credit accounts that match `caFilter` in credit managers matching `marketFilter`
132
+ function countCreditAccounts(MarketFilter memory marketFilter, CreditAccountFilter memory caFilter)
133
+ external
134
+ view
135
+ returns (uint256)
136
+ {
137
+ address[] memory creditManagers = _getCreditManagers(marketFilter);
138
+ return _countCreditAccounts(creditManagers, caFilter, 0, type(uint256).max);
208
139
  }
209
140
 
210
141
  /// @notice Counts credit accounts that match `caFilter` in a given `creditManager`
211
- function countCreditAccounts(
212
- address creditManager,
213
- CreditAccountFilter memory caFilter,
214
- Pagination memory pagination
215
- ) public view returns (uint256) {
216
- uint256 offset = pagination.offset;
217
- uint256 scanLimit = pagination.scanLimit == 0 ? type(uint256).max : pagination.scanLimit;
218
-
219
- uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
220
- if (len <= offset) return 0;
221
- uint256 limit = Math.min(len - offset, scanLimit);
222
-
223
- return _countCreditAccounts(creditManager, caFilter, offset, limit);
142
+ function countCreditAccounts(address creditManager, CreditAccountFilter memory caFilter)
143
+ external
144
+ view
145
+ returns (uint256)
146
+ {
147
+ address[] memory creditManagers = new address[](1);
148
+ creditManagers[0] = creditManager;
149
+ return _countCreditAccounts(creditManagers, caFilter, 0, type(uint256).max);
224
150
  }
225
151
 
226
152
  // --------- //
@@ -229,37 +155,108 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
229
155
 
230
156
  /// @dev Querying implementation
231
157
  function _getCreditAccounts(
232
- address creditManager,
158
+ address[] memory creditManagers,
233
159
  CreditAccountFilter memory filter,
234
- CreditAccountData[] memory data,
235
- uint256 dataOffset,
236
160
  uint256 offset,
237
161
  uint256 limit
238
- ) internal view returns (uint256 nextDataOffset, uint256 scanned) {
239
- address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, limit);
240
- for (uint256 i; i < creditAccounts.length; ++i) {
241
- if (_checkFilterMatch(creditAccounts[i], creditManager, filter)) {
242
- data[dataOffset++] = _getCreditAccountData(creditAccounts[i], creditManager);
162
+ ) internal view returns (CreditAccountData[] memory data, uint256 nextOffset) {
163
+ uint256 num = _countCreditAccounts(creditManagers, filter, offset, limit);
164
+ if (num == 0) return (data, 0);
165
+
166
+ // allocating the `CreditAccountData` array might consume most of the gas leaving no room for computations,
167
+ // so we instead allocate and gradually fill the array of pointers to structs which takes much less space
168
+ bytes32[] memory dataPointers = new bytes32[](num);
169
+ uint256 dataOffset;
170
+
171
+ // to adjust to RPC provider's call gas limit, the function stops when gas left gets below gas reserve, which
172
+ // starts at this number that should be enough to cover a single account processing, and increases with each
173
+ // new data struct to accommodate the cost of memory expansion that happens upon ABI-encoding returned data
174
+ uint256 gasReserve = 2e6;
175
+
176
+ nextOffset = offset;
177
+ for (uint256 i; i < creditManagers.length; ++i) {
178
+ address creditManager = creditManagers[i];
179
+ uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
180
+
181
+ // first, we need to get to the `offset` position
182
+ if (len <= offset) {
183
+ offset -= len;
184
+ continue;
243
185
  }
244
186
 
245
- // either finished or reached one of return size or gas limits
246
- if (dataOffset == data.length || gasleft() < GAS_THRESHOLD) return (dataOffset, i + 1);
187
+ uint256 count = Math.min(len - offset, limit);
188
+ address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, count);
189
+
190
+ // circumvent the "Stack too deep." error
191
+ CreditAccountFilter memory filter_ = filter;
192
+
193
+ for (uint256 j; j < creditAccounts.length; ++j) {
194
+ address creditAccount = creditAccounts[j];
195
+ if (_checkFilterMatch(creditAccount, creditManager, filter_)) {
196
+ uint256 gasBefore = gasleft();
197
+
198
+ CreditAccountData memory d = _getCreditAccountData(creditAccount, creditManager);
199
+ ++dataOffset;
200
+ assembly {
201
+ // save the pointer to created struct
202
+ mstore(add(dataPointers, mul(0x20, dataOffset)), d)
203
+ }
204
+
205
+ // rough approximation of gas that will be needed to accommodate additional memory expansion cost
206
+ gasReserve += (gasBefore - gasleft()) / 2;
207
+ }
208
+ --count;
209
+
210
+ if (dataOffset == num || gasleft() < gasReserve) break;
211
+ }
212
+
213
+ nextOffset += creditAccounts.length - count;
214
+ if (dataOffset == num || count != 0) break;
215
+
216
+ limit -= creditAccounts.length;
217
+ if (limit == 0) break;
218
+ offset = 0;
219
+ }
220
+
221
+ assembly {
222
+ // cast array of pointers to structs to array of structs
223
+ data := dataPointers
224
+ // trim array to its actual size
225
+ mstore(data, dataOffset)
247
226
  }
248
- return (dataOffset, creditAccounts.length);
227
+
228
+ // set `nextOffset` to zero to indicate that scanning is finished
229
+ if (dataOffset == num) nextOffset = 0;
249
230
  }
250
231
 
251
232
  /// @dev Counting implementation
252
233
  function _countCreditAccounts(
253
- address creditManager,
234
+ address[] memory creditManagers,
254
235
  CreditAccountFilter memory filter,
255
236
  uint256 offset,
256
237
  uint256 limit
257
238
  ) internal view returns (uint256 num) {
258
- address[] memory creditAccounts = ICreditManagerV3(creditManager).creditAccounts(offset, limit);
259
- for (uint256 i; i < creditAccounts.length; ++i) {
260
- if (_checkFilterMatch(creditAccounts[i], creditManager, filter)) {
261
- ++num;
239
+ for (uint256 i; i < creditManagers.length; ++i) {
240
+ address creditManager = creditManagers[i];
241
+ uint256 len = ICreditManagerV3(creditManager).creditAccountsLen();
242
+
243
+ // first, we need to get to the `offset` position
244
+ if (len <= offset) {
245
+ offset -= len;
246
+ continue;
262
247
  }
248
+
249
+ address[] memory creditAccounts =
250
+ ICreditManagerV3(creditManager).creditAccounts(offset, Math.min(len - offset, limit));
251
+ for (uint256 j; j < creditAccounts.length; ++j) {
252
+ if (_checkFilterMatch(creditAccounts[j], creditManager, filter)) {
253
+ ++num;
254
+ }
255
+ }
256
+
257
+ limit -= creditAccounts.length;
258
+ if (limit == 0) break;
259
+ offset = 0;
263
260
  }
264
261
  }
265
262
 
@@ -274,6 +271,7 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
274
271
  data.creditFacade = ICreditManagerV3(creditManager).creditFacade();
275
272
  data.underlying = ICreditManagerV3(creditManager).underlying();
276
273
  data.owner = ICreditManagerV3(creditManager).getBorrowerOrRevert(creditAccount);
274
+ data.expirationDate = ICreditFacadeV3(data.creditFacade).expirationDate();
277
275
 
278
276
  CollateralDebtData memory cdd =
279
277
  ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_ONLY);
@@ -285,21 +283,41 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
285
283
  // collateral is computed separately since it might revert on `balanceOf` and `latestRoundData` calls
286
284
  try ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL)
287
285
  returns (CollateralDebtData memory cdd_) {
288
- data.totalDebtUSD = cdd.totalDebtUSD;
286
+ data.totalDebtUSD = cdd_.totalDebtUSD;
289
287
  data.totalValueUSD = cdd_.totalValueUSD;
290
288
  data.twvUSD = cdd_.twvUSD;
291
- data.totalValue = cdd.totalValue;
289
+ data.totalValue = cdd_.totalValue;
292
290
 
293
- data.healthFactor = cdd.twvUSD * PERCENTAGE_FACTOR >= type(uint16).max * cdd.totalDebtUSD
291
+ data.healthFactor = cdd_.twvUSD * PERCENTAGE_FACTOR >= type(uint16).max * cdd_.totalDebtUSD
294
292
  ? type(uint16).max
295
- : uint16(cdd.twvUSD * PERCENTAGE_FACTOR / cdd.totalDebtUSD);
293
+ : uint16((cdd_.twvUSD * PERCENTAGE_FACTOR) / cdd_.totalDebtUSD);
296
294
  data.success = true;
297
295
  } catch {}
298
296
 
299
- uint256 numTokens = ICreditManagerV3(creditManager).collateralTokensCount();
297
+ uint256 maxTokens = ICreditManagerV3(creditManager).collateralTokensCount();
298
+
299
+ // the function is called for every account, so allocating an array of size `maxTokens` and trimming it
300
+ // might cause issues with memory expansion and we must count the precise number of tokens in advance
301
+ uint256 numTokens;
302
+ uint256 returnedTokensMask;
303
+ for (uint256 k; k < maxTokens; ++k) {
304
+ uint256 mask = 1 << k;
305
+ if (cdd.enabledTokensMask & mask == 0) {
306
+ address token = ICreditManagerV3(creditManager).getTokenByMask(mask);
307
+ try IERC20(token).balanceOf(creditAccount) returns (uint256 balance) {
308
+ if (balance <= 1) continue;
309
+ } catch {
310
+ continue;
311
+ }
312
+ }
313
+ ++numTokens;
314
+ returnedTokensMask |= mask;
315
+ }
316
+
300
317
  data.tokens = new TokenInfo[](numTokens);
301
- for (uint256 i; i < numTokens; ++i) {
302
- uint256 mask = 1 << i;
318
+ uint256 i;
319
+ while (returnedTokensMask != 0) {
320
+ uint256 mask = returnedTokensMask & uint256(-int256(returnedTokensMask));
303
321
  address token = ICreditManagerV3(creditManager).getTokenByMask(mask);
304
322
  data.tokens[i].token = token;
305
323
  data.tokens[i].mask = mask;
@@ -313,6 +331,9 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
313
331
  (data.tokens[i].quota,) =
314
332
  IPoolQuotaKeeperV3(cdd._poolQuotaKeeper).getQuotaAndOutstandingInterest(creditAccount, token);
315
333
  }
334
+
335
+ returnedTokensMask ^= mask;
336
+ ++i;
316
337
  }
317
338
  }
318
339
 
@@ -338,7 +359,7 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
338
359
  if (filter.reverting) return false;
339
360
  uint16 healthFactor = cdd.twvUSD * PERCENTAGE_FACTOR >= type(uint16).max * cdd.totalDebtUSD
340
361
  ? type(uint16).max
341
- : uint16(cdd.twvUSD * PERCENTAGE_FACTOR / cdd.totalDebtUSD);
362
+ : uint16((cdd.twvUSD * PERCENTAGE_FACTOR) / cdd.totalDebtUSD);
342
363
  if (filter.minHealthFactor != 0 && healthFactor < filter.minHealthFactor) return false;
343
364
  if (filter.maxHealthFactor != 0 && healthFactor > filter.maxHealthFactor) return false;
344
365
  } catch {
@@ -350,45 +371,34 @@ contract CreditAccountCompressor is IVersion, SanityCheckTrait {
350
371
  }
351
372
 
352
373
  /// @dev Credit managers discovery
353
- function _getCreditManagers(CreditManagerFilter memory filter)
354
- internal
355
- view
356
- returns (address[] memory creditManagers)
357
- {
358
- address[] memory configurators = IAddressProviderV3(ADDRESS_PROVIDER).marketConfigurators();
374
+ function _getCreditManagers(MarketFilter memory filter) internal view returns (address[] memory creditManagers) {
375
+ address[] memory configurators = filter.configurators.length != 0
376
+ ? filter.configurators
377
+ : IMarketConfiguratorFactory(marketConfiguratorFactory).getMarketConfigurators();
359
378
 
360
379
  // rough estimate of maximum number of credit managers
361
380
  uint256 max;
362
381
  for (uint256 i; i < configurators.length; ++i) {
363
- address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
382
+ address cr = IMarketConfigurator(configurators[i]).contractsRegister();
364
383
  max += IContractsRegister(cr).getCreditManagers().length;
365
384
  }
366
385
 
367
- // allocate the array with maximum potentially needed size (total number of credit managers
368
- // can be assumed to be relatively small, so memary expansion cost is not an issue here)
386
+ // allocate the array with maximum potentially needed size (total number of credit managers can be assumed
387
+ // to be relatively small and the function is only called once, so memory expansion cost is not an issue)
369
388
  creditManagers = new address[](max);
370
389
  uint256 num;
371
390
  for (uint256 i; i < configurators.length; ++i) {
372
- if (filter.curator != address(0)) {
373
- if (IMarketConfiguratorV3(configurators[i]).owner() != filter.curator) continue;
374
- }
375
-
376
- address cr = IMarketConfiguratorV3(configurators[i]).contractsRegister();
391
+ address cr = IMarketConfigurator(configurators[i]).contractsRegister();
377
392
  address[] memory managers = IContractsRegister(cr).getCreditManagers();
378
393
  for (uint256 j; j < managers.length; ++j) {
379
- // we're only concerned with v3 contracts
380
- uint256 ver = IVersion(managers[j]).version();
381
- if (ver < 3_00 || ver > 3_99) continue;
382
-
383
- if (filter.pool != address(0)) {
384
- if (ICreditManagerV3(managers[j]).pool() != filter.pool) continue;
385
- }
386
-
387
- if (filter.underlying != address(0)) {
388
- if (ICreditManagerV3(managers[j]).underlying() != filter.underlying) continue;
394
+ address manager = managers[j];
395
+ // FIXME: there's room for optimization that allows to avoid scanning over all configurators
396
+ if (filter.pools.length != 0 && !filter.pools.contains(ICreditManagerV3(manager).pool())) continue;
397
+ if (filter.underlying != address(0) && ICreditManagerV3(manager).underlying() != filter.underlying) {
398
+ continue;
389
399
  }
390
400
 
391
- creditManagers[num++] = managers[j];
401
+ creditManagers[num++] = manager;
392
402
  }
393
403
  }
394
404