@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.
- package/.docker/Dockerfile +17 -0
- package/.dockerignore +7 -0
- package/.env.sample +24 -0
- package/.gitlab-ci.yml +51 -0
- package/.gitmodules +15 -0
- package/.prettierrc +10 -0
- package/.solcover.js +4 -0
- package/.vscode/settings.json +23 -0
- package/LICENSE.MD +51 -0
- package/README.md +135 -0
- package/contracts/arbitrum/contracts/controllers/UniswapV2ControllerArbitrum.sol +37 -0
- package/contracts/arbitrum/contracts/controllers/UniswapV3ControllerArbitrum.sol +46 -0
- package/contracts/arbitrum/contracts/oracle/PriceOracleArbitrum.sol +51 -0
- package/contracts/main/contracts/controllers/Controller.sol +61 -0
- package/contracts/main/contracts/controllers/IController.sol +81 -0
- package/contracts/main/contracts/controllers/OneInchV5Controller.sol +332 -0
- package/contracts/main/contracts/controllers/UnoswapV2Controller.sol +789 -0
- package/contracts/main/contracts/controllers/UnoswapV3Controller.sol +1018 -0
- package/contracts/main/contracts/core/CoreWhitelist.sol +192 -0
- package/contracts/main/contracts/core/ICoreWhitelist.sol +92 -0
- package/contracts/main/contracts/core/IUFarmCore.sol +95 -0
- package/contracts/main/contracts/core/UFarmCore.sol +402 -0
- package/contracts/main/contracts/fund/FundFactory.sol +59 -0
- package/contracts/main/contracts/fund/IUFarmFund.sol +68 -0
- package/contracts/main/contracts/fund/UFarmFund.sol +504 -0
- package/contracts/main/contracts/oracle/ChainlinkedOracle.sol +71 -0
- package/contracts/main/contracts/oracle/IChainlinkAggregator.sol +18 -0
- package/contracts/main/contracts/oracle/IPriceOracle.sol +55 -0
- package/contracts/main/contracts/oracle/PriceOracle.sol +20 -0
- package/contracts/main/contracts/oracle/PriceOracleCore.sol +212 -0
- package/contracts/main/contracts/oracle/WstETHOracle.sol +64 -0
- package/contracts/main/contracts/permissions/Permissions.sol +54 -0
- package/contracts/main/contracts/permissions/UFarmPermissionsModel.sol +136 -0
- package/contracts/main/contracts/pool/IPoolAdmin.sol +57 -0
- package/contracts/main/contracts/pool/IUFarmPool.sol +304 -0
- package/contracts/main/contracts/pool/PerformanceFeeLib.sol +81 -0
- package/contracts/main/contracts/pool/PoolAdmin.sol +437 -0
- package/contracts/main/contracts/pool/PoolFactory.sol +74 -0
- package/contracts/main/contracts/pool/PoolWhitelist.sol +70 -0
- package/contracts/main/contracts/pool/UFarmPool.sol +959 -0
- package/contracts/main/shared/AssetController.sol +194 -0
- package/contracts/main/shared/ECDSARecover.sol +91 -0
- package/contracts/main/shared/NZGuard.sol +99 -0
- package/contracts/main/shared/SafeOPS.sol +128 -0
- package/contracts/main/shared/UFarmCoreLink.sol +83 -0
- package/contracts/main/shared/UFarmErrors.sol +16 -0
- package/contracts/main/shared/UFarmMathLib.sol +80 -0
- package/contracts/main/shared/UFarmOwnableUUPS.sol +59 -0
- package/contracts/main/shared/UFarmOwnableUUPSBeacon.sol +34 -0
- package/contracts/test/Block.sol +15 -0
- package/contracts/test/InchSwapTestProxy.sol +292 -0
- package/contracts/test/MockPoolAdmin.sol +8 -0
- package/contracts/test/MockUFarmPool.sol +8 -0
- package/contracts/test/MockV3wstETHstETHAgg.sol +128 -0
- package/contracts/test/MockedWETH9.sol +72 -0
- package/contracts/test/OneInchToUFarmTestEnv.sol +466 -0
- package/contracts/test/StableCoin.sol +25 -0
- package/contracts/test/UFarmMockSequencerUptimeFeed.sol +44 -0
- package/contracts/test/UFarmMockV3Aggregator.sol +145 -0
- package/contracts/test/UUPSBlock.sol +19 -0
- package/contracts/test/ufarmLocal/MulticallV3.sol +220 -0
- package/contracts/test/ufarmLocal/controllers/UniswapV2ControllerUFarm.sol +27 -0
- package/contracts/test/ufarmLocal/controllers/UniswapV3ControllerUFarm.sol +43 -0
- package/deploy/100_test_env_setup.ts +483 -0
- package/deploy/20_deploy_uniV2.ts +48 -0
- package/deploy/21_create_pairs_uniV2.ts +149 -0
- package/deploy/22_deploy_mocked_aggregators.ts +123 -0
- package/deploy/22_deploy_wsteth_oracle.ts +65 -0
- package/deploy/23_deploy_uniV3.ts +80 -0
- package/deploy/24_create_pairs_uniV3.ts +140 -0
- package/deploy/25_deploy_oneInch.ts +38 -0
- package/deploy/2_deploy_multicall.ts +34 -0
- package/deploy/30_deploy_price_oracle.ts +33 -0
- package/deploy/3_deploy_lido.ts +114 -0
- package/deploy/40_deploy_pool_beacon.ts +19 -0
- package/deploy/41_deploy_poolAdmin_beacon.ts +19 -0
- package/deploy/42_deploy_ufarmcore.ts +29 -0
- package/deploy/43_deploy_fund_beacon.ts +19 -0
- package/deploy/4_deploy_tokens.ts +76 -0
- package/deploy/50_deploy_poolFactory.ts +35 -0
- package/deploy/51_deploy_fundFactory.ts +29 -0
- package/deploy/60_init_contracts.ts +101 -0
- package/deploy/61_whitelist_tokens.ts +18 -0
- package/deploy/70_deploy_uniV2Controller.ts +70 -0
- package/deploy/71_deploy_uniV3Controller.ts +67 -0
- package/deploy/72_deploy_oneInchController.ts +25 -0
- package/deploy/79_whitelist_controllers.ts +125 -0
- package/deploy/ufarm/arbitrum/1_prepare_env.ts +82 -0
- package/deploy/ufarm/arbitrum/2_deploy_ufarm.ts +178 -0
- package/deploy/ufarm/arbitrum-sepolia/1000_prepare_arb_sepolia_env.ts +308 -0
- package/deploy-config.json +112 -0
- package/deploy-data/oracles.csv +32 -0
- package/deploy-data/protocols.csv +10 -0
- package/deploy-data/tokens.csv +32 -0
- package/docker-compose.yml +67 -0
- package/hardhat.config.ts +449 -0
- package/index.js +93 -0
- package/package.json +82 -0
- package/scripts/_deploy_helpers.ts +992 -0
- package/scripts/_deploy_network_options.ts +49 -0
- package/scripts/activatePool.ts +51 -0
- package/scripts/createPool.ts +62 -0
- package/scripts/deploy_1inch_proxy.ts +98 -0
- package/scripts/pool-data.ts +420 -0
- package/scripts/post-deploy.sh +24 -0
- package/scripts/setUniV2Rate.ts +252 -0
- package/scripts/swapOneInchV5.ts +94 -0
- package/scripts/swapUniswapV2.ts +65 -0
- package/scripts/swapUniswapV3.ts +71 -0
- package/scripts/test.ts +61 -0
- package/scripts/typings-copy-artifacts.ts +83 -0
- package/tasks/boostPool.ts +39 -0
- package/tasks/createFund.ts +44 -0
- package/tasks/deboostPool.ts +48 -0
- package/tasks/grantUFarmPermissions.ts +57 -0
- package/tasks/index.ts +7 -0
- package/tasks/mintUSDT.ts +62 -0
- package/test/Periphery.test.ts +640 -0
- package/test/PriceOracle.test.ts +82 -0
- package/test/TestCases.MD +109 -0
- package/test/UFarmCore.test.ts +331 -0
- package/test/UFarmFund.test.ts +406 -0
- package/test/UFarmPool.test.ts +4736 -0
- package/test/_fixtures.ts +783 -0
- package/test/_helpers.ts +2195 -0
- package/test/_oneInchTestData.ts +632 -0
- 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
|
+
}
|