@oldzeppelin/contract 1.1.1

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 (127) hide show
  1. package/.docker/Dockerfile +17 -0
  2. package/.dockerignore +7 -0
  3. package/.env.sample +24 -0
  4. package/.gitlab-ci.yml +51 -0
  5. package/.gitmodules +15 -0
  6. package/.prettierrc +10 -0
  7. package/.solcover.js +4 -0
  8. package/.vscode/settings.json +23 -0
  9. package/LICENSE.MD +51 -0
  10. package/README.md +135 -0
  11. package/contracts/arbitrum/contracts/controllers/UniswapV2ControllerArbitrum.sol +37 -0
  12. package/contracts/arbitrum/contracts/controllers/UniswapV3ControllerArbitrum.sol +46 -0
  13. package/contracts/arbitrum/contracts/oracle/PriceOracleArbitrum.sol +51 -0
  14. package/contracts/main/contracts/controllers/Controller.sol +61 -0
  15. package/contracts/main/contracts/controllers/IController.sol +81 -0
  16. package/contracts/main/contracts/controllers/OneInchV5Controller.sol +332 -0
  17. package/contracts/main/contracts/controllers/UnoswapV2Controller.sol +789 -0
  18. package/contracts/main/contracts/controllers/UnoswapV3Controller.sol +1018 -0
  19. package/contracts/main/contracts/core/CoreWhitelist.sol +192 -0
  20. package/contracts/main/contracts/core/ICoreWhitelist.sol +92 -0
  21. package/contracts/main/contracts/core/IUFarmCore.sol +95 -0
  22. package/contracts/main/contracts/core/UFarmCore.sol +402 -0
  23. package/contracts/main/contracts/fund/FundFactory.sol +59 -0
  24. package/contracts/main/contracts/fund/IUFarmFund.sol +68 -0
  25. package/contracts/main/contracts/fund/UFarmFund.sol +504 -0
  26. package/contracts/main/contracts/oracle/ChainlinkedOracle.sol +71 -0
  27. package/contracts/main/contracts/oracle/IChainlinkAggregator.sol +18 -0
  28. package/contracts/main/contracts/oracle/IPriceOracle.sol +55 -0
  29. package/contracts/main/contracts/oracle/PriceOracle.sol +20 -0
  30. package/contracts/main/contracts/oracle/PriceOracleCore.sol +212 -0
  31. package/contracts/main/contracts/oracle/WstETHOracle.sol +64 -0
  32. package/contracts/main/contracts/permissions/Permissions.sol +54 -0
  33. package/contracts/main/contracts/permissions/UFarmPermissionsModel.sol +136 -0
  34. package/contracts/main/contracts/pool/IPoolAdmin.sol +57 -0
  35. package/contracts/main/contracts/pool/IUFarmPool.sol +304 -0
  36. package/contracts/main/contracts/pool/PerformanceFeeLib.sol +81 -0
  37. package/contracts/main/contracts/pool/PoolAdmin.sol +437 -0
  38. package/contracts/main/contracts/pool/PoolFactory.sol +74 -0
  39. package/contracts/main/contracts/pool/PoolWhitelist.sol +70 -0
  40. package/contracts/main/contracts/pool/UFarmPool.sol +959 -0
  41. package/contracts/main/shared/AssetController.sol +194 -0
  42. package/contracts/main/shared/ECDSARecover.sol +91 -0
  43. package/contracts/main/shared/NZGuard.sol +99 -0
  44. package/contracts/main/shared/SafeOPS.sol +128 -0
  45. package/contracts/main/shared/UFarmCoreLink.sol +83 -0
  46. package/contracts/main/shared/UFarmErrors.sol +16 -0
  47. package/contracts/main/shared/UFarmMathLib.sol +80 -0
  48. package/contracts/main/shared/UFarmOwnableUUPS.sol +59 -0
  49. package/contracts/main/shared/UFarmOwnableUUPSBeacon.sol +34 -0
  50. package/contracts/test/Block.sol +15 -0
  51. package/contracts/test/InchSwapTestProxy.sol +292 -0
  52. package/contracts/test/MockPoolAdmin.sol +8 -0
  53. package/contracts/test/MockUFarmPool.sol +8 -0
  54. package/contracts/test/MockV3wstETHstETHAgg.sol +128 -0
  55. package/contracts/test/MockedWETH9.sol +72 -0
  56. package/contracts/test/OneInchToUFarmTestEnv.sol +466 -0
  57. package/contracts/test/StableCoin.sol +25 -0
  58. package/contracts/test/UFarmMockSequencerUptimeFeed.sol +44 -0
  59. package/contracts/test/UFarmMockV3Aggregator.sol +145 -0
  60. package/contracts/test/UUPSBlock.sol +19 -0
  61. package/contracts/test/ufarmLocal/MulticallV3.sol +220 -0
  62. package/contracts/test/ufarmLocal/controllers/UniswapV2ControllerUFarm.sol +27 -0
  63. package/contracts/test/ufarmLocal/controllers/UniswapV3ControllerUFarm.sol +43 -0
  64. package/deploy/100_test_env_setup.ts +483 -0
  65. package/deploy/20_deploy_uniV2.ts +48 -0
  66. package/deploy/21_create_pairs_uniV2.ts +149 -0
  67. package/deploy/22_deploy_mocked_aggregators.ts +123 -0
  68. package/deploy/22_deploy_wsteth_oracle.ts +65 -0
  69. package/deploy/23_deploy_uniV3.ts +80 -0
  70. package/deploy/24_create_pairs_uniV3.ts +140 -0
  71. package/deploy/25_deploy_oneInch.ts +38 -0
  72. package/deploy/2_deploy_multicall.ts +34 -0
  73. package/deploy/30_deploy_price_oracle.ts +33 -0
  74. package/deploy/3_deploy_lido.ts +114 -0
  75. package/deploy/40_deploy_pool_beacon.ts +19 -0
  76. package/deploy/41_deploy_poolAdmin_beacon.ts +19 -0
  77. package/deploy/42_deploy_ufarmcore.ts +29 -0
  78. package/deploy/43_deploy_fund_beacon.ts +19 -0
  79. package/deploy/4_deploy_tokens.ts +76 -0
  80. package/deploy/50_deploy_poolFactory.ts +35 -0
  81. package/deploy/51_deploy_fundFactory.ts +29 -0
  82. package/deploy/60_init_contracts.ts +101 -0
  83. package/deploy/61_whitelist_tokens.ts +18 -0
  84. package/deploy/70_deploy_uniV2Controller.ts +70 -0
  85. package/deploy/71_deploy_uniV3Controller.ts +67 -0
  86. package/deploy/72_deploy_oneInchController.ts +25 -0
  87. package/deploy/79_whitelist_controllers.ts +125 -0
  88. package/deploy/ufarm/arbitrum/1_prepare_env.ts +82 -0
  89. package/deploy/ufarm/arbitrum/2_deploy_ufarm.ts +178 -0
  90. package/deploy/ufarm/arbitrum-sepolia/1000_prepare_arb_sepolia_env.ts +308 -0
  91. package/deploy-config.json +112 -0
  92. package/deploy-data/oracles.csv +32 -0
  93. package/deploy-data/protocols.csv +10 -0
  94. package/deploy-data/tokens.csv +32 -0
  95. package/docker-compose.yml +67 -0
  96. package/hardhat.config.ts +449 -0
  97. package/index.js +93 -0
  98. package/package.json +82 -0
  99. package/scripts/_deploy_helpers.ts +992 -0
  100. package/scripts/_deploy_network_options.ts +49 -0
  101. package/scripts/activatePool.ts +51 -0
  102. package/scripts/createPool.ts +62 -0
  103. package/scripts/deploy_1inch_proxy.ts +98 -0
  104. package/scripts/pool-data.ts +420 -0
  105. package/scripts/post-deploy.sh +24 -0
  106. package/scripts/setUniV2Rate.ts +252 -0
  107. package/scripts/swapOneInchV5.ts +94 -0
  108. package/scripts/swapUniswapV2.ts +65 -0
  109. package/scripts/swapUniswapV3.ts +71 -0
  110. package/scripts/test.ts +61 -0
  111. package/scripts/typings-copy-artifacts.ts +83 -0
  112. package/tasks/boostPool.ts +39 -0
  113. package/tasks/createFund.ts +44 -0
  114. package/tasks/deboostPool.ts +48 -0
  115. package/tasks/grantUFarmPermissions.ts +57 -0
  116. package/tasks/index.ts +7 -0
  117. package/tasks/mintUSDT.ts +62 -0
  118. package/test/Periphery.test.ts +640 -0
  119. package/test/PriceOracle.test.ts +82 -0
  120. package/test/TestCases.MD +109 -0
  121. package/test/UFarmCore.test.ts +331 -0
  122. package/test/UFarmFund.test.ts +406 -0
  123. package/test/UFarmPool.test.ts +4736 -0
  124. package/test/_fixtures.ts +783 -0
  125. package/test/_helpers.ts +2195 -0
  126. package/test/_oneInchTestData.ts +632 -0
  127. package/tsconfig.json +12 -0
@@ -0,0 +1,959 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+
3
+ pragma solidity ^0.8.0;
4
+
5
+ /// INTERFACES
6
+ import {AssetsStructs} from '../../shared/AssetController.sol';
7
+ import {ICoreWhitelist} from '../core/CoreWhitelist.sol';
8
+ import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
9
+ import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
10
+ import {IERC721Controller, IERC20SynthController} from '../controllers/IController.sol';
11
+ import {IUFarmCore} from '../core/IUFarmCore.sol';
12
+ import {IUFarmFund} from '../fund/IUFarmFund.sol';
13
+ import {IUFarmPool} from './IUFarmPool.sol';
14
+ import {IPoolAdmin} from './IPoolAdmin.sol';
15
+ import {IPriceOracle} from '../oracle/IPriceOracle.sol';
16
+ import {Permissions} from '../permissions/Permissions.sol';
17
+
18
+ /// CONTRACTS
19
+ import {ECDSARecover} from '../../shared/ECDSARecover.sol';
20
+ import {ERC20Upgradeable as ERC20} from '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol';
21
+ import {ERC721HolderUpgradeable as ERC721Holder} from '@openzeppelin/contracts-upgradeable/token/ERC721/utils/ERC721HolderUpgradeable.sol';
22
+ import {NZGuard} from '../../shared/NZGuard.sol';
23
+ import {PoolWhitelist} from './PoolWhitelist.sol';
24
+ import {ReentrancyGuardUpgradeable as ReentrancyGuard} from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
25
+ import {UFarmErrors} from '../../shared/UFarmErrors.sol';
26
+ import {UFarmOwnableUUPSBeacon} from '../../shared/UFarmOwnableUUPSBeacon.sol';
27
+ import {UFarmPermissionsModel} from '../permissions/UFarmPermissionsModel.sol';
28
+
29
+ /// LIBRARIES
30
+ import {AssetLib} from '../../shared/AssetController.sol';
31
+ import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
32
+ import {PerformanceFeeLib} from './PerformanceFeeLib.sol';
33
+ import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
34
+ import {SafeOPS} from '../../shared/SafeOPS.sol';
35
+
36
+ /**
37
+ * @title UFarmPool contract
38
+ * @author https://ufarm.digital/
39
+ * @notice Pool implementation contract for UFarm Funds
40
+ */
41
+ contract UFarmPool is
42
+ IUFarmPool,
43
+ ERC20,
44
+ UFarmErrors,
45
+ PoolWhitelist,
46
+ ERC721Holder,
47
+ ReentrancyGuard,
48
+ NZGuard,
49
+ ECDSARecover,
50
+ UFarmOwnableUUPSBeacon
51
+ {
52
+ using EnumerableSet for EnumerableSet.AddressSet;
53
+ using EnumerableSet for EnumerableSet.UintSet;
54
+ using AssetLib for AssetsStructs.Assets;
55
+ using SafeERC20 for IERC20;
56
+
57
+ uint256 private constant HALF = 5e17;
58
+ uint256 private constant ONE = 1e18;
59
+ uint256 private constant TEN_PERCENTS = 1e17;
60
+ uint256 private constant YEAR = 365 days;
61
+ uint256 public constant MAX_TOKEN_WEIGHT = 25;
62
+
63
+ PoolStatus public status;
64
+
65
+ address private _ufarmCore;
66
+
67
+ address public ufarmFund;
68
+ address public valueToken;
69
+ address public poolAdmin;
70
+
71
+ uint256 public highWaterMark;
72
+ uint256 public lastAccrual;
73
+
74
+ /**
75
+ * @inheritdoc IUFarmPool
76
+ */
77
+ address public _protocolTarget;
78
+ bytes32 public _withdrawalHash;
79
+ bytes32 private protocolInUse;
80
+
81
+ /**
82
+ * @notice Mapping of the pending deposit requests to receiving timestamp
83
+ */
84
+ mapping(bytes32 => uint256) public pendingWithdrawalsRequests;
85
+ mapping(bytes32 => bool) private __usedDepositsRequests;
86
+ mapping(bytes32 => bool) private __usedWithdrawalsRequests;
87
+ AssetsStructs.Assets private __assets;
88
+
89
+ uint8 private __decimals;
90
+ bool private __isMangerAction;
91
+
92
+ modifier keepWithdrawalHash(bytes32 _withdHash) {
93
+ _withdrawalHash = _withdHash;
94
+ _;
95
+ delete _withdrawalHash;
96
+ }
97
+
98
+ /**
99
+ * @notice Allows function to be called only from the controller
100
+ */
101
+ modifier calledByController() {
102
+ if (msg.sender != address(this)) revert NotDelegateCalled();
103
+ _;
104
+ }
105
+
106
+ /**
107
+ * @notice Reverts if value Pool status is not equal to `_onlyStatus`
108
+ */
109
+ modifier onlyStatus(PoolStatus _onlyStatus) {
110
+ _statusIs(_onlyStatus);
111
+ _;
112
+ }
113
+
114
+ /**
115
+ * @notice Reverts if the UFarm platform is paused
116
+ */
117
+ modifier ufarmIsNotPaused() {
118
+ _ufarmIsNotPaused();
119
+ _;
120
+ }
121
+
122
+ /**
123
+ * @notice Reverts if caller is not a fund member
124
+ */
125
+ modifier onlyFundMember() {
126
+ _isCallerFundMember();
127
+ _;
128
+ }
129
+
130
+ modifier onlyActiveFund() {
131
+ _checkActiveFund();
132
+ _;
133
+ }
134
+
135
+ function __init_UFarmPool(
136
+ CreationSettingsWithLinks memory _settings,
137
+ address _poolAdmin
138
+ ) external initializer checkDelegateCall {
139
+ __ERC20_init(_settings.params.name, _settings.params.symbol);
140
+ __ReentrancyGuard_init();
141
+ __init_UFarmOwnableUUPSBeacon();
142
+ __init_UFarmPool_unchained(_settings, _poolAdmin);
143
+ }
144
+
145
+ /**
146
+ * @notice Initializes pool simultaneously with its creation
147
+ * @param _settings - pool settings struct
148
+ */
149
+ function __init_UFarmPool_unchained(
150
+ CreationSettingsWithLinks memory _settings,
151
+ address _poolAdmin
152
+ ) internal onlyInitializing {
153
+ address _valueToken = _settings.params.valueToken;
154
+
155
+ (ufarmFund, _ufarmCore, poolAdmin, valueToken) = (
156
+ _settings.ufarmFund,
157
+ _settings.ufarmCore,
158
+ _poolAdmin,
159
+ _valueToken
160
+ );
161
+
162
+ if (!ICoreWhitelist(_ufarmCore).isTokenWhitelisted(_valueToken))
163
+ revert TokenIsNotAllowed(_valueToken);
164
+
165
+ __decimals = ERC20(_valueToken).decimals();
166
+
167
+ _changeStatus(PoolStatus.Created);
168
+ }
169
+
170
+ //// ASSET MANAGEMENT FUNCTIONS
171
+
172
+ /**
173
+ * @inheritdoc PoolWhitelist
174
+ */
175
+ function isTokenAllowed(address token) public view override(PoolWhitelist) returns (bool) {
176
+ return (token == valueToken) || super.isTokenAllowed(token);
177
+ }
178
+
179
+ //// MAIN FUNCTIONS
180
+ function version() public pure override returns (string memory) {
181
+ return '1.0';
182
+ }
183
+
184
+ function name() public view override(ECDSARecover, ERC20) returns (string memory) {
185
+ return ERC20.name();
186
+ }
187
+
188
+ /**
189
+ * @inheritdoc ERC20
190
+ */
191
+ function decimals() public view override(ERC20, IUFarmPool) returns (uint8) {
192
+ return __decimals;
193
+ }
194
+
195
+ /**
196
+ * @inheritdoc IUFarmPool
197
+ */
198
+ function ufarmCore() public view override(IUFarmPool, PoolWhitelist) returns (address) {
199
+ return _ufarmCore;
200
+ }
201
+
202
+ /**
203
+ * @notice Returns the current pool exchange rate
204
+ */
205
+ function getExchangeRate() public view override returns (uint256 exchangeRate) {
206
+ uint256 totalCost = getTotalCost();
207
+ uint256 presicion = 10 ** decimals();
208
+
209
+ IPoolAdmin.PoolConfig memory config = IPoolAdmin(poolAdmin).getConfig();
210
+
211
+ (
212
+ uint256 protocolFee,
213
+ uint256 managementFee,
214
+ uint256 performanceFee,
215
+ uint256 sharesToUFarm,
216
+ uint256 sharesToFund
217
+ ) = _calculateFee(totalCost, config.managementCommission, config.packedPerformanceFee);
218
+
219
+ return
220
+ (totalCost == 0)
221
+ ? presicion
222
+ : (totalCost * presicion) / (totalSupply() + sharesToUFarm + sharesToFund);
223
+ }
224
+
225
+ /**
226
+ * @inheritdoc IUFarmPool
227
+ */
228
+ function getTotalCost() public view override returns (uint256 totalCost) {
229
+ return
230
+ IPriceOracle(IUFarmCore(_ufarmCore).priceOracle()).getTotalCostOfPool(
231
+ address(this),
232
+ valueToken
233
+ );
234
+ }
235
+
236
+ /**
237
+ * @inheritdoc IUFarmPool
238
+ */
239
+ function erc20CommonAssets() external view override returns (address[] memory tokenAssets) {
240
+ return __assets.erc20CommonAssets();
241
+ }
242
+
243
+ /**
244
+ * @inheritdoc IUFarmPool
245
+ */
246
+ function erc20ControlledAssets()
247
+ external
248
+ view
249
+ override
250
+ returns (AssetsStructs.ControlledERC20[] memory liquidityAssetsERC20)
251
+ {
252
+ return __assets.erc20ControlledAssets();
253
+ }
254
+
255
+ /**
256
+ * @inheritdoc IUFarmPool
257
+ */
258
+ function erc721ControlledAssets()
259
+ external
260
+ view
261
+ returns (/*override*/ AssetsStructs.ControlledERC721[] memory liquidityAssetsERC721)
262
+ {
263
+ return __assets.erc721ControlledAssets();
264
+ }
265
+
266
+ /**
267
+ * @inheritdoc IUFarmPool
268
+ */
269
+ function deposit(
270
+ uint256 _amountToInvest
271
+ )
272
+ external
273
+ override
274
+ ufarmIsNotPaused
275
+ nonZeroValue(_amountToInvest)
276
+ nonReentrant
277
+ returns (uint256 toMint)
278
+ {
279
+ _checkStatusForFinancing(true);
280
+ IPoolAdmin.PoolConfig memory config = IPoolAdmin(poolAdmin).getConfig();
281
+
282
+ if (
283
+ (msg.sender != ufarmFund) &&
284
+ (_amountToInvest < config.minInvestment || _amountToInvest > config.maxInvestment)
285
+ ) revert InvalidInvestmentAmount(_amountToInvest, config.minInvestment, config.maxInvestment);
286
+
287
+ uint256 totalAssetsCost = getTotalCost();
288
+ _accrueFee(totalAssetsCost, config.managementCommission, config.packedPerformanceFee);
289
+
290
+ IERC20(valueToken).safeTransferFrom(msg.sender, address(this), _amountToInvest);
291
+
292
+ _addERC20(valueToken, bytes32(0));
293
+
294
+ highWaterMark += _amountToInvest;
295
+
296
+ toMint = _mintSharesByQuote(msg.sender, _amountToInvest, totalAssetsCost);
297
+
298
+ emit Deposit(msg.sender, valueToken, _amountToInvest, toMint);
299
+ // TODO: make optional hook in deposit to invoke something on controller, to restake, as example
300
+ }
301
+
302
+ /**
303
+ * @notice Validates deposit request and returns investor address, amount to invest and deposit request hash
304
+ * @param depositRequestuest - signed deposit request struct
305
+ * @return investor - investor address
306
+ * @return amountToInvest - amount to invest
307
+ * @return depositRequestHash - whole signed deposit request hash
308
+ */
309
+ function validateDepositRequest(
310
+ SignedDepositRequest memory depositRequestuest
311
+ ) public view returns (address investor, uint256 amountToInvest, bytes32 depositRequestHash) {
312
+ depositRequestHash = ECDSARecover.toEIP712MessageHash(
313
+ DOMAIN_SEPARATOR(),
314
+ _hashDepositReqBody(depositRequestuest.body)
315
+ );
316
+
317
+ if (__usedDepositsRequests[depositRequestHash]) revert UFarmErrors.ActionAlreadyDone();
318
+
319
+ investor = ECDSARecover.recoverAddress(depositRequestHash, depositRequestuest.signature);
320
+ if (investor == address(0)) revert ECDSARecover.WrongSignature();
321
+
322
+ if (block.timestamp > depositRequestuest.body.deadline)
323
+ revert DeadlineExpired(depositRequestuest.body.deadline, block.timestamp);
324
+
325
+ if (depositRequestuest.body.poolAddr != address(this))
326
+ revert AnotherPoolExpected(address(this), depositRequestuest.body.poolAddr);
327
+
328
+ amountToInvest = depositRequestuest.body.amountToInvest;
329
+
330
+ if (amountToInvest == 0) revert InvalidInvestmentAmount(amountToInvest, 0, type(uint256).max);
331
+ }
332
+
333
+ /**
334
+ * @notice Validates withdrawal request and returns investor address, shares to burn and withdrawal request hash
335
+ * @param withdRequest - signed withdrawal request struct
336
+ * @return investor - investor address
337
+ * @return sharesToBurn - amount of shares to burn
338
+ * @return withdrawRequestHash - whole signed withdrawal request hash
339
+ */
340
+ function validateWithdrawalRequest(
341
+ SignedWithdrawalRequest memory withdRequest
342
+ ) public view returns (address investor, uint256 sharesToBurn, bytes32 withdrawRequestHash) {
343
+ withdrawRequestHash = ECDSARecover.toEIP712MessageHash(
344
+ DOMAIN_SEPARATOR(),
345
+ _hashWithdrawReqBody(withdRequest.body)
346
+ );
347
+
348
+ if (__usedWithdrawalsRequests[withdrawRequestHash]) revert UFarmErrors.ActionAlreadyDone();
349
+
350
+ investor = ECDSARecover.recoverAddress(withdrawRequestHash, withdRequest.signature);
351
+ if (investor == address(0)) revert ECDSARecover.WrongSignature();
352
+
353
+ if (withdRequest.body.poolAddr != address(this))
354
+ revert AnotherPoolExpected(address(this), withdRequest.body.poolAddr);
355
+
356
+ sharesToBurn = withdRequest.body.sharesToBurn;
357
+
358
+ if (sharesToBurn > balanceOf(investor) || sharesToBurn == 0)
359
+ revert InvalidWithdrawalAmount(sharesToBurn, balanceOf(investor));
360
+ }
361
+
362
+ function approveDeposits(
363
+ SignedDepositRequest[] calldata _depositRequests
364
+ ) external ufarmIsNotPaused nonReentrant onlyFundMember {
365
+ _checkStatusForFinancing(true);
366
+
367
+ uint256 requestsLength = _depositRequests.length;
368
+ _nonEmptyArray(requestsLength);
369
+
370
+ IPoolAdmin _poolAdmin = IPoolAdmin(poolAdmin);
371
+
372
+ _poolAdmin.checkPoolOrFundPermission(
373
+ msg.sender,
374
+ Permissions.Pool.ApprovePoolTopup,
375
+ Permissions.Fund.ApprovePoolTopup
376
+ );
377
+ IPoolAdmin.PoolConfig memory config = _poolAdmin.getConfig();
378
+
379
+ uint256 totalCost = getTotalCost();
380
+ _accrueFee(totalCost, config.managementCommission, config.packedPerformanceFee);
381
+
382
+ // solidity gas optimization
383
+ address investor;
384
+ uint256 sharesToMint;
385
+ uint256 amountToInvest;
386
+ bytes32 depositRequestHash;
387
+ uint256 totalDeposit;
388
+
389
+ for (uint256 i; i < requestsLength; ++i) {
390
+ // Validate each deposit request
391
+ (investor, amountToInvest, depositRequestHash) = validateDepositRequest(_depositRequests[i]);
392
+ __usedDepositsRequests[depositRequestHash] = true;
393
+
394
+ // Process the deposit
395
+ IERC20(valueToken).safeTransferFrom(investor, address(this), amountToInvest);
396
+ sharesToMint = _mintSharesByQuote(investor, amountToInvest, totalCost);
397
+
398
+ // Adjust the total cost and total deposit
399
+ totalCost += amountToInvest;
400
+ totalDeposit += amountToInvest;
401
+
402
+ emit Deposit(investor, valueToken, amountToInvest, sharesToMint);
403
+ emit DepositRequestExecuted(investor, depositRequestHash);
404
+ }
405
+
406
+ _addERC20(valueToken, bytes32(0));
407
+ highWaterMark += totalDeposit;
408
+ }
409
+
410
+ function approveWithdrawals(
411
+ SignedWithdrawalRequest[] calldata _withdrawRequests
412
+ ) external ufarmIsNotPaused nonReentrant onlyFundMember {
413
+ _checkStatusForFinancing(false);
414
+
415
+ uint256 requestsLength = _withdrawRequests.length;
416
+ _nonEmptyArray(requestsLength);
417
+
418
+ IPoolAdmin _poolAdmin = IPoolAdmin(poolAdmin);
419
+
420
+ _poolAdmin.checkPoolOrFundPermission(
421
+ msg.sender,
422
+ Permissions.Pool.ApprovePoolWithdrawals,
423
+ Permissions.Fund.ApprovePoolWithdrawals
424
+ );
425
+ IPoolAdmin.PoolConfig memory config = _poolAdmin.getConfig();
426
+
427
+ uint256 totalCost = getTotalCost();
428
+ _accrueFee(totalCost, config.managementCommission, config.packedPerformanceFee);
429
+
430
+ // solidity gas optimization
431
+ address investor;
432
+ uint256 sharesToBurn;
433
+ uint256 amountToWithdraw;
434
+ bytes32 withdrawalRequestHash;
435
+
436
+ for (uint256 i; i < requestsLength; ++i) {
437
+ // Validate each withdrawal request
438
+ (investor, sharesToBurn, withdrawalRequestHash) = validateWithdrawalRequest(
439
+ _withdrawRequests[i]
440
+ );
441
+
442
+ // Mark the request as used
443
+ __usedWithdrawalsRequests[withdrawalRequestHash] = true;
444
+
445
+ // Delete the request from the pending withdrawals
446
+ delete pendingWithdrawalsRequests[withdrawalRequestHash];
447
+
448
+ // Process the withdrawal
449
+ amountToWithdraw = _processWithdrawal(
450
+ investor,
451
+ sharesToBurn,
452
+ totalCost,
453
+ withdrawalRequestHash
454
+ );
455
+
456
+ // Adjust the total cost
457
+ totalCost -= amountToWithdraw;
458
+ }
459
+ }
460
+
461
+ /**
462
+ * @inheritdoc IUFarmPool
463
+ */
464
+ function withdraw(
465
+ SignedWithdrawalRequest calldata _withdrawalRequest
466
+ ) external override ufarmIsNotPaused nonReentrant returns (uint256 burnedAssetsCost) {
467
+ _checkStatusForFinancing(false);
468
+ IPoolAdmin.PoolConfig memory config = IPoolAdmin(poolAdmin).getConfig();
469
+ uint256 totalAssetsCost = getTotalCost();
470
+ _accrueFee(totalAssetsCost, config.managementCommission, config.packedPerformanceFee);
471
+
472
+ if (msg.sender == ufarmFund) {
473
+ // Check for the mandatory shares if pool is still active
474
+ if (status < PoolStatus.Deactivating) {
475
+ uint256 mandatoryShares = (IUFarmCore(_ufarmCore).minimumFundDeposit() * totalSupply()) /
476
+ totalAssetsCost;
477
+ uint256 totalUserShares = balanceOf(msg.sender);
478
+
479
+ uint256 availableToWithdraw = totalUserShares > mandatoryShares
480
+ ? totalUserShares - mandatoryShares
481
+ : 0;
482
+
483
+ if (_withdrawalRequest.body.sharesToBurn > availableToWithdraw)
484
+ revert InvalidWithdrawalAmount(_withdrawalRequest.body.sharesToBurn, availableToWithdraw);
485
+ }
486
+ return
487
+ _processWithdrawal(
488
+ msg.sender,
489
+ _withdrawalRequest.body.sharesToBurn,
490
+ totalAssetsCost,
491
+ keccak256(abi.encode(blockhash(block.number), totalSupply())) // Pseudo-random hash
492
+ );
493
+ } else {
494
+ // Check if the withdrawal request is valid
495
+ (
496
+ address investor,
497
+ uint256 sharesToBurn,
498
+ bytes32 withdrawalRequestHash
499
+ ) = validateWithdrawalRequest(_withdrawalRequest);
500
+
501
+ if (config.withdrawalLockupPeriod > 0) {
502
+ if (pendingWithdrawalsRequests[withdrawalRequestHash] == 0) {
503
+ // Set the withdrawal request timestamp
504
+ pendingWithdrawalsRequests[withdrawalRequestHash] = block.timestamp;
505
+ emit WithdrawRequestReceived(investor, withdrawalRequestHash, block.timestamp);
506
+ return 0;
507
+ } else {
508
+ // Check if the lockup period has passed
509
+ uint256 unlockTime = pendingWithdrawalsRequests[withdrawalRequestHash] +
510
+ config.withdrawalLockupPeriod;
511
+ if (block.timestamp < unlockTime)
512
+ // Safe because of the check above
513
+ revert LockupPeriodNotPassed(unlockTime);
514
+ }
515
+ }
516
+
517
+ // Mark the request as used
518
+ __usedWithdrawalsRequests[withdrawalRequestHash] = true;
519
+
520
+ // Delete the request from the pending withdrawals
521
+ delete pendingWithdrawalsRequests[withdrawalRequestHash];
522
+
523
+ return _processWithdrawal(investor, sharesToBurn, totalAssetsCost, withdrawalRequestHash);
524
+ }
525
+ }
526
+
527
+ function _processWithdrawal(
528
+ address investor,
529
+ uint256 sharesToBurn,
530
+ uint256 _totalcost,
531
+ bytes32 withdrawalRequestHash
532
+ ) private keepWithdrawalHash(withdrawalRequestHash) returns (uint256 burnedAssetsCost) {
533
+ uint256 _totalSupply = totalSupply();
534
+ burnedAssetsCost = (_totalcost * sharesToBurn) / _totalSupply;
535
+
536
+ _burn(investor, sharesToBurn);
537
+ if (IERC20(valueToken).balanceOf(address(this)) > burnedAssetsCost) {
538
+ IERC20(valueToken).safeTransfer(investor, burnedAssetsCost);
539
+ _tryRemoveERC20(valueToken);
540
+ emit Withdraw(investor, valueToken, burnedAssetsCost, withdrawalRequestHash);
541
+ } else {
542
+ address asset;
543
+ uint256 length;
544
+
545
+ // Avoiding stack too deep error
546
+ {
547
+ // ERC721 -> ERC20
548
+ address controllerAddr;
549
+ bytes32 controllerHash;
550
+ bytes[] memory forceSellTxs;
551
+
552
+ uint256[] memory ids;
553
+
554
+ length = __assets.erc721Controlled.assets.length();
555
+ for (uint256 i; i < length; ++i) {
556
+ asset = __assets.erc721Controlled.assets.at(i);
557
+ controllerHash = __assets.erc721Controlled.controllers[asset];
558
+ controllerAddr = _getProtocolController(controllerHash);
559
+
560
+ ids = __assets.erc721Controlled.idsOfAsset[asset].values();
561
+
562
+ for (uint256 j; j < ids.length; ++j) {
563
+ forceSellTxs = IERC721Controller(controllerAddr).encodePartialWithdrawalERC721(
564
+ asset,
565
+ ids[j],
566
+ sharesToBurn,
567
+ _totalSupply
568
+ );
569
+
570
+ for (uint256 k; k < forceSellTxs.length; ++k) {
571
+ _protocolAction(true, controllerAddr, controllerHash, investor, forceSellTxs[k]);
572
+ }
573
+ }
574
+ }
575
+
576
+ // Synth ERC20 -> ERC20
577
+ length = __assets.erc20Controlled.assets.length();
578
+ for (uint256 i; i < length; ++i) {
579
+ asset = __assets.erc20Controlled.assets.at(i);
580
+ controllerHash = __assets.erc20Controlled.controllers[asset];
581
+ controllerAddr = _getProtocolController(controllerHash);
582
+
583
+ forceSellTxs = IERC20SynthController(controllerAddr).encodePartialWithdrawalERC20(
584
+ asset,
585
+ sharesToBurn,
586
+ _totalSupply
587
+ );
588
+
589
+ for (uint256 j; j < forceSellTxs.length; ++j) {
590
+ _protocolAction(true, controllerAddr, controllerHash, investor, forceSellTxs[j]);
591
+ }
592
+ }
593
+ }
594
+
595
+ // Common ERC20
596
+ uint256 assetBalance;
597
+ uint256 toWithdraw;
598
+ address[] memory commonAssets = __assets.erc20CommonAssets();
599
+
600
+ length = commonAssets.length;
601
+ for (uint256 i; i < length; ++i) {
602
+ asset = commonAssets[i];
603
+ assetBalance = IERC20(asset).balanceOf(address(this));
604
+ toWithdraw = (assetBalance * sharesToBurn) / _totalSupply;
605
+ if (toWithdraw > 0) {
606
+ IERC20(asset).safeTransfer(investor, toWithdraw);
607
+ _tryRemoveERC20(asset);
608
+ emit Withdraw(investor, asset, toWithdraw, withdrawalRequestHash);
609
+ }
610
+ }
611
+ }
612
+
613
+ highWaterMark -= highWaterMark > burnedAssetsCost ? burnedAssetsCost : highWaterMark;
614
+
615
+ emit WithdrawRequestExecuted(investor, sharesToBurn, withdrawalRequestHash);
616
+
617
+ return burnedAssetsCost;
618
+ }
619
+
620
+ /**
621
+ * @notice Allows to call any function of the protocol controller
622
+ * @param _protocol - protocol name
623
+ * @param _data - encoded function call of controller with selector and arguments
624
+ */
625
+ function protocolAction(
626
+ bytes32 _protocol,
627
+ bytes calldata _data
628
+ ) external ufarmIsNotPaused onlyFundMember nonReentrant {
629
+ _checkProtocolAllowance(_protocol);
630
+ _statusBeforeOrThis(PoolStatus.Deactivating);
631
+ IPoolAdmin(poolAdmin).isAbleToManageFunds(msg.sender);
632
+ __isMangerAction = true;
633
+ _protocolAction(false, _getProtocolController(_protocol), _protocol, address(this), _data);
634
+ delete __isMangerAction;
635
+ }
636
+
637
+ /**
638
+ * @notice Changes pool status to `_newStatus`
639
+ * @dev Only the PoolAdmin contract can call this function
640
+ * @param _newStatus - new pool status
641
+ */
642
+ function changeStatus(PoolStatus _newStatus) external override {
643
+ if (msg.sender != poolAdmin) revert UFarmErrors.NonAuthorized();
644
+
645
+ _changeStatus(_newStatus);
646
+ }
647
+
648
+ /**
649
+ * @dev Only for Controllers. Adds ERC20 asset to the pool assets list
650
+ */
651
+ function addERC20(address _asset, bytes32 _controllerOrZero) external calledByController {
652
+ _addERC20(_asset, _controllerOrZero);
653
+ }
654
+
655
+ /**
656
+ * @dev Only for Controllers. Removes ERC20 asset from the pool assets list
657
+ */
658
+ function removeERC20(address _asset) external calledByController {
659
+ _tryRemoveERC20(_asset);
660
+ }
661
+
662
+ function addERC721(address _asset, uint256[] memory _ids) external calledByController {
663
+ _addERC721WithController(_asset, _ids, protocolInUse);
664
+ }
665
+
666
+ function removeERC721(address _asset, uint256[] memory _ids) external calledByController {
667
+ _removeERC721WithController(_asset, _ids);
668
+ }
669
+
670
+ //// INTERNAL FUNCTIONS
671
+
672
+ function _checkStatusForFinancing(bool isDeposit) private view {
673
+ IUFarmFund.FundStatus fundStatus = IUFarmFund(ufarmFund).status();
674
+
675
+ if (msg.sender == ufarmFund) {
676
+ if (isDeposit) {
677
+ // Fund can't deposit if it's not in Active or Approved status
678
+ if (fundStatus > IUFarmFund.FundStatus.Active)
679
+ revert IUFarmFund.WrongFundStatus(IUFarmFund.FundStatus.Active, fundStatus);
680
+ // Fund can't deposit if pool is Terminated
681
+ _statusBeforeOrThis(PoolStatus.Deactivating);
682
+ } else {
683
+ // Fund can't withdraw if it's Blocked
684
+ if (fundStatus == IUFarmFund.FundStatus.Blocked) {
685
+ revert IUFarmFund.WrongFundStatus(IUFarmFund.FundStatus.Terminated, fundStatus);
686
+ }
687
+ }
688
+ } else {
689
+ if (isDeposit) {
690
+ // Investor can't deposit if fund isn't Active
691
+ if (fundStatus != IUFarmFund.FundStatus.Active) {
692
+ revert IUFarmFund.WrongFundStatus(IUFarmFund.FundStatus.Active, fundStatus);
693
+ }
694
+ // Investor can't deposit if pool isn't Active
695
+ _statusIs(PoolStatus.Active);
696
+ }
697
+ // Investor can withdraw regardless of fund or pool status
698
+ }
699
+ }
700
+
701
+ function _checkActiveFund() private view {
702
+ IUFarmFund.FundStatus fundStatus = IUFarmFund(ufarmFund).status();
703
+ if (fundStatus != IUFarmFund.FundStatus.Active) {
704
+ revert IUFarmFund.WrongFundStatus(IUFarmFund.FundStatus.Active, fundStatus);
705
+ }
706
+ }
707
+
708
+ function _isCallerFundMember() private view {
709
+ if (
710
+ !UFarmPermissionsModel(address(ufarmFund)).hasPermission(
711
+ msg.sender,
712
+ uint8(Permissions.Fund.Member)
713
+ )
714
+ ) revert UFarmErrors.NonAuthorized();
715
+ }
716
+
717
+ /**
718
+ * @notice Reverts if the pool status is not equal to `_requiredStatus`
719
+ * @param _requiredStatus - required pool status
720
+ */
721
+ function _statusIs(PoolStatus _requiredStatus) private view {
722
+ if (status != _requiredStatus) revert InvalidPoolStatus(_requiredStatus, status);
723
+ }
724
+
725
+ /**
726
+ * @notice Reverts if the pool status is not equal to `_lastAllowedStatus` or any status before it
727
+ */
728
+ function _statusBeforeOrThis(PoolStatus _lastAllowedStatus) private view {
729
+ if (status > _lastAllowedStatus)
730
+ revert InvalidPoolStatus(PoolStatus(_lastAllowedStatus), status);
731
+ }
732
+
733
+ function _protocolAction(
734
+ bool _ignoreRevert,
735
+ address _controllerAddr,
736
+ bytes32 _protocolHash,
737
+ address _target,
738
+ bytes memory _data
739
+ ) private checkDelegateCall {
740
+ protocolInUse = _protocolHash;
741
+ _protocolTarget = _target;
742
+ SafeOPS._safeDelegateCall(_ignoreRevert, _controllerAddr, _data);
743
+ delete protocolInUse;
744
+ delete _protocolTarget;
745
+
746
+ emit SuccessfullControllerCall(_protocolHash);
747
+ }
748
+
749
+ /**
750
+ * @notice Returns controller address by its name
751
+ * @param _protocol - protocol name
752
+ * @return controllerAddr - controller address
753
+ */
754
+ function _getProtocolController(bytes32 _protocol) private view returns (address controllerAddr) {
755
+ controllerAddr = IUFarmCore(_ufarmCore).controllers(_protocol);
756
+ if (controllerAddr == address(0)) revert FETCHING_CONTROLLER_FAILED();
757
+ }
758
+
759
+ /**
760
+ * @notice Reverts if the UFarm platform is paused
761
+ */
762
+ function _ufarmIsNotPaused() private view {
763
+ if (IUFarmCore(_ufarmCore).isPaused()) revert UFarmIsPaused();
764
+ }
765
+
766
+ /**
767
+ * @notice Accrues fees and mints corresponding pool shares.
768
+ * @param totalCost The total cost value of the pool.
769
+ * @param managementCommission The management commission rate.
770
+ * @param packedPerformanceCommission The performance commission rate.
771
+ */
772
+ function _accrueFee(
773
+ uint256 totalCost,
774
+ uint256 managementCommission,
775
+ uint256 packedPerformanceCommission
776
+ ) private {
777
+ // When pool is created, lastAccrual is 0, so we need to set it to current timestamp
778
+ // and set HWM to totalCost
779
+ if (lastAccrual == 0) {
780
+ lastAccrual = block.timestamp;
781
+ highWaterMark = totalCost;
782
+ return;
783
+ }
784
+
785
+ (
786
+ uint256 protocolFee,
787
+ uint256 managementFee,
788
+ uint256 performanceFee,
789
+ uint256 sharesToUFarm,
790
+ uint256 sharesToFund
791
+ ) = _calculateFee(totalCost, managementCommission, packedPerformanceCommission);
792
+
793
+ if (totalCost > highWaterMark) highWaterMark = totalCost;
794
+
795
+ bool mintedToCore = _mintShares(_ufarmCore, sharesToUFarm);
796
+ bool mintedToFund = _mintShares(ufarmFund, sharesToFund);
797
+
798
+ if (mintedToCore || mintedToFund) lastAccrual = block.timestamp;
799
+
800
+ emit FeeAccrued(protocolFee, managementFee, performanceFee, sharesToUFarm, sharesToFund);
801
+ }
802
+
803
+ function _calculateFee(
804
+ uint256 totalCost,
805
+ uint256 managementCommission,
806
+ uint256 packedPerformanceCommission
807
+ )
808
+ private
809
+ view
810
+ returns (
811
+ uint256 protocolFee,
812
+ uint256 managementFee,
813
+ uint256 performanceFee,
814
+ uint256 sharesToUFarm,
815
+ uint256 sharesToFund
816
+ )
817
+ {
818
+ uint256 accrualTime = block.timestamp - lastAccrual;
819
+
820
+ if (lastAccrual == 0 || accrualTime == 0) {
821
+ return (0, 0, 0, 0, 0);
822
+ }
823
+
824
+ {
825
+ uint256 protocolCommission = IUFarmCore(_ufarmCore).protocolCommission();
826
+ uint256 costInTime = (totalCost * accrualTime) / YEAR;
827
+
828
+ (protocolFee, managementFee) = (
829
+ (costInTime * protocolCommission) / ONE,
830
+ (costInTime * managementCommission) / ONE
831
+ );
832
+ }
833
+
834
+ if (totalCost > highWaterMark) {
835
+ uint256 profit = totalCost - highWaterMark;
836
+
837
+ performanceFee = (profit * PerformanceFeeLib.ONE_HUNDRED_PERCENT) / highWaterMark; // APY ratio
838
+ uint16 performanceCommission = performanceFee > PerformanceFeeLib.MAX_COMMISSION_STEP
839
+ ? PerformanceFeeLib.MAX_COMMISSION_STEP
840
+ : uint16(performanceFee); // Compare with max commission step, normalizing to MAX_COMMISSION_STEP
841
+
842
+ performanceCommission = PerformanceFeeLib._getPerformanceCommission(
843
+ packedPerformanceCommission,
844
+ performanceCommission
845
+ ); // Unpack commission percent for the step, where step is APY multiplier
846
+
847
+ performanceFee = (profit * performanceCommission) / PerformanceFeeLib.ONE_HUNDRED_PERCENT; // Profit * commission rate
848
+ }
849
+ uint256 totalFundFee = (4 * (performanceFee + managementFee)) / 5; // 80%
850
+ uint256 totalUFarmFee = totalFundFee / 4 + protocolFee; // 20% + protocol fee
851
+
852
+ uint256 _totalSupply = totalSupply();
853
+
854
+ sharesToUFarm = _sharesByQuote(totalUFarmFee, _totalSupply, totalCost);
855
+ sharesToFund = _sharesByQuote(totalFundFee, _totalSupply + sharesToUFarm, totalCost);
856
+ }
857
+
858
+ /**
859
+ * @notice Calculates the number of shares equivalent to the the value amount and mints them to the fee recipient.
860
+ * @param to The address to mint the fee shares to.
861
+ * @param quoteAmount The total amount in value token.
862
+ * @param totalCost The total cost value of the pool.
863
+ * @return sharesMinted The number of shares minted.
864
+ */
865
+ function _mintSharesByQuote(
866
+ address to,
867
+ uint256 quoteAmount,
868
+ uint256 totalCost
869
+ ) internal returns (uint256 sharesMinted) {
870
+ uint256 _totalSupply = totalSupply();
871
+ sharesMinted = _sharesByQuote(quoteAmount, _totalSupply, totalCost);
872
+ _mintShares(to, sharesMinted);
873
+ }
874
+
875
+ function _sharesByQuote(
876
+ uint256 quoteAmount,
877
+ uint256 _totalSupply,
878
+ uint256 totalCost
879
+ ) internal pure returns (uint256 shares) {
880
+ shares = (totalCost > 0 && _totalSupply > 0)
881
+ ? ((quoteAmount * _totalSupply) / totalCost)
882
+ : quoteAmount;
883
+ }
884
+
885
+ function _mintShares(address to, uint256 sharesToMint) internal returns (bool success) {
886
+ if (sharesToMint == 0) return false;
887
+ _mint(to, sharesToMint);
888
+ return true;
889
+ }
890
+
891
+ function _hashDepositReqBody(
892
+ DepositRequest memory depositRequestuest
893
+ ) private pure returns (bytes32) {
894
+ return
895
+ keccak256(
896
+ abi.encode(
897
+ keccak256(
898
+ 'DepositRequest(uint256 amountToInvest,bytes32 salt,address poolAddr,uint96 deadline)'
899
+ ),
900
+ depositRequestuest.amountToInvest,
901
+ depositRequestuest.salt,
902
+ depositRequestuest.poolAddr,
903
+ depositRequestuest.deadline
904
+ )
905
+ );
906
+ }
907
+
908
+ function _hashWithdrawReqBody(
909
+ WithdrawRequest memory withdrawRequest
910
+ ) private pure returns (bytes32) {
911
+ return
912
+ keccak256(
913
+ abi.encode(
914
+ keccak256('WithdrawRequest(uint256 sharesToBurn,bytes32 salt,address poolAddr)'),
915
+ withdrawRequest.sharesToBurn,
916
+ withdrawRequest.salt,
917
+ withdrawRequest.poolAddr
918
+ )
919
+ );
920
+ }
921
+
922
+ function _changeStatus(PoolStatus _newStatus) private {
923
+ status = _newStatus;
924
+ emit PoolStatusChanged(_newStatus);
925
+ }
926
+
927
+ function _addERC20(address _asset, bytes32 _controllerAddr) private {
928
+ // Do not add pure ERC20 token if it is not whitelisted
929
+ if (_controllerAddr == bytes32(0) && !isTokenAllowed(_asset)) return;
930
+
931
+ __assets.addERC20(_asset, _controllerAddr);
932
+ _checkMaxAssetWeight();
933
+ }
934
+
935
+ function _tryRemoveERC20(address _asset) private {
936
+ __assets.removeERC20(_asset);
937
+ }
938
+
939
+ function _addERC721WithController(
940
+ address _asset,
941
+ uint256[] memory _ids,
942
+ bytes32 _controller
943
+ ) private {
944
+ __assets.addERC721WithController(_asset, _ids, _controller);
945
+ _checkMaxAssetWeight();
946
+ }
947
+
948
+ function _removeERC721WithController(address _asset, uint256[] memory _ids) private {
949
+ __assets.removeERC721WithController(_asset, _ids);
950
+ }
951
+
952
+ function _checkMaxAssetWeight() private view {
953
+ if (__isMangerAction && __assets.totalWeight > MAX_TOKEN_WEIGHT) {
954
+ revert AssetsWeightAboveMax(__assets.totalWeight);
955
+ }
956
+ }
957
+
958
+ uint256[50] private __gap;
959
+ }