@ensuro/core 1.2.0

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 (55) hide show
  1. package/README.md +93 -0
  2. package/build/contracts/AaveAssetManager.sol/AaveAssetManager.json +772 -0
  3. package/build/contracts/BaseAssetManager.sol/BaseAssetManager.json +494 -0
  4. package/build/contracts/DataTypes.sol/DataTypes.json +10 -0
  5. package/build/contracts/EToken.sol/EToken.json +1148 -0
  6. package/build/contracts/FlightDelayRiskModule.sol/FlightDelayRiskModule.json +1093 -0
  7. package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +412 -0
  8. package/build/contracts/Policy.sol/Policy.json +10 -0
  9. package/build/contracts/PolicyNFT.sol/PolicyNFT.json +570 -0
  10. package/build/contracts/PolicyPool.sol/PolicyPool.json +1257 -0
  11. package/build/contracts/PolicyPoolComponent.sol/PolicyPoolComponent.json +281 -0
  12. package/build/contracts/PolicyPoolConfig.sol/PolicyPoolConfig.json +660 -0
  13. package/build/contracts/RiskModule.sol/RiskModule.json +748 -0
  14. package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +1032 -0
  15. package/build/contracts/WadRayMath.sol/WadRayMath.json +10 -0
  16. package/build/contracts/dependencies/ChainlinkClientUpgradeable.sol/ChainlinkClientUpgradeable.json +50 -0
  17. package/build/contracts/upgraded/IPolicyNFTv1.sol/IPolicyNFTv1.json +322 -0
  18. package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1.json +571 -0
  19. package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1_Upgrade.json +602 -0
  20. package/build/interfaces/IAssetManager.sol/IAssetManager.json +31 -0
  21. package/build/interfaces/IEToken.sol/IEToken.json +491 -0
  22. package/build/interfaces/IInsolvencyHook.sol/IInsolvencyHook.json +42 -0
  23. package/build/interfaces/ILPWhitelist.sol/ILPWhitelist.json +74 -0
  24. package/build/interfaces/IPolicyNFT.sol/IPolicyNFT.json +321 -0
  25. package/build/interfaces/IPolicyPool.sol/IPolicyPool.json +445 -0
  26. package/build/interfaces/IPolicyPoolComponent.sol/IPolicyPoolComponent.json +24 -0
  27. package/build/interfaces/IPolicyPoolConfig.sol/IPolicyPoolConfig.json +328 -0
  28. package/build/interfaces/IRiskModule.sol/IRiskModule.json +141 -0
  29. package/contracts/AaveAssetManager.sol +326 -0
  30. package/contracts/BaseAssetManager.sol +283 -0
  31. package/contracts/DataTypes.sol +287 -0
  32. package/contracts/EToken.sol +773 -0
  33. package/contracts/FlightDelayRiskModule.sol +208 -0
  34. package/contracts/LPManualWhitelist.sol +57 -0
  35. package/contracts/Policy.sol +104 -0
  36. package/contracts/PolicyNFT.sol +96 -0
  37. package/contracts/PolicyPool.sol +716 -0
  38. package/contracts/PolicyPoolComponent.sol +164 -0
  39. package/contracts/PolicyPoolConfig.sol +231 -0
  40. package/contracts/RiskModule.sol +323 -0
  41. package/contracts/TrustfulRiskModule.sol +81 -0
  42. package/contracts/WadRayMath.sol +135 -0
  43. package/contracts/dependencies/ChainlinkClientUpgradeable.sol +371 -0
  44. package/contracts/upgraded/IPolicyNFTv1.sol +15 -0
  45. package/contracts/upgraded/PolicyNFTv1.sol +126 -0
  46. package/interfaces/IAssetManager.sol +23 -0
  47. package/interfaces/IEToken.sol +44 -0
  48. package/interfaces/IInsolvencyHook.sol +27 -0
  49. package/interfaces/ILPWhitelist.sol +23 -0
  50. package/interfaces/IPolicyNFT.sol +15 -0
  51. package/interfaces/IPolicyPool.sol +44 -0
  52. package/interfaces/IPolicyPoolComponent.sol +13 -0
  53. package/interfaces/IPolicyPoolConfig.sol +81 -0
  54. package/interfaces/IRiskModule.sol +29 -0
  55. package/package.json +30 -0
@@ -0,0 +1,326 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
+ import {BaseAssetManager} from "./BaseAssetManager.sol";
6
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
8
+ import {IPolicyPoolConfig} from "../interfaces/IPolicyPoolConfig.sol";
9
+ import {WadRayMath} from "./WadRayMath.sol";
10
+ import {ILendingPoolAddressesProvider} from "@aave/protocol-v2/contracts/interfaces/ILendingPoolAddressesProvider.sol";
11
+ import {ILendingPool} from "@aave/protocol-v2/contracts/interfaces/ILendingPool.sol";
12
+ import {IAToken} from "@aave/protocol-v2/contracts/interfaces/IAToken.sol";
13
+ import {IPriceOracle} from "@aave/protocol-v2/contracts/interfaces/IPriceOracle.sol";
14
+ import {AaveProtocolDataProvider} from "@aave/protocol-v2/contracts/misc/AaveProtocolDataProvider.sol";
15
+ import {IUniswapV2Router02} from "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
16
+
17
+ /**
18
+ * @title AssetManager that reinvests the capital in AAVE
19
+ * @dev Deposits and withdraw from AAVE, also converts the rewards.
20
+ * Invest into AAVE AToken for the underlying asset (ex. USDC) getting lending interests.
21
+ * When needs to deinvest, first deinvest from AAVE, but also can liquidate the rewards
22
+ * (AAVE in mainnet or MATIC in Polygon) using a DEX.
23
+ * Above a given threshold, the rewards are claimed. Also, above a given threshold they are also reinvested
24
+ * to accrue additional interests and rewards.
25
+ * An authorized user (SWAP_REWARDS_ROLE) can force the swap of the rewards for the pool's currency.
26
+ * @custom:security-contact security@ensuro.co
27
+ * @author Ensuro
28
+ */
29
+ contract AaveAssetManager is BaseAssetManager {
30
+ using SafeERC20 for IERC20Metadata;
31
+ using WadRayMath for uint256;
32
+
33
+ bytes32 public constant SWAP_REWARDS_ROLE = keccak256("SWAP_REWARDS_ROLE");
34
+
35
+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
36
+ ILendingPoolAddressesProvider internal immutable _aaveAddrProv;
37
+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
38
+ IAToken internal immutable _aToken;
39
+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
40
+ IAToken internal immutable _rewardAToken;
41
+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
42
+ IERC20Metadata internal immutable _rewardToken;
43
+ /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
44
+ IUniswapV2Router02 internal immutable _swapRouter; // We will use SushiSwap in Polygon
45
+ uint256 internal _claimRewardsMin; // Minimum amount of rewards accumulated to claim
46
+ uint256 internal _reinvestRewardsMin; // Minimum amount of rewards to reinvest into AAVE
47
+ uint256 internal _maxSlippage; // Maximum slippage in WAD
48
+
49
+ bytes32 internal constant DATA_PROVIDER_ID =
50
+ 0x0100000000000000000000000000000000000000000000000000000000000000;
51
+
52
+ uint256 internal constant SECONDS_PER_YEAR = 365 days;
53
+
54
+ event RewardSwapped(uint256 rewardIn, uint256 currencyOut);
55
+
56
+ /// @custom:oz-upgrades-unsafe-allow constructor
57
+ constructor(
58
+ IPolicyPool policyPool_,
59
+ ILendingPoolAddressesProvider aaveAddrProv_,
60
+ IUniswapV2Router02 swapRouter_
61
+ ) BaseAssetManager(policyPool_) {
62
+ _aaveAddrProv = aaveAddrProv_;
63
+ _swapRouter = swapRouter_;
64
+ AaveProtocolDataProvider dataProvider = AaveProtocolDataProvider(
65
+ aaveAddrProv_.getAddress(DATA_PROVIDER_ID)
66
+ );
67
+ (address aToken_, , ) = dataProvider.getReserveTokensAddresses(address(policyPool_.currency()));
68
+ _aToken = IAToken(aToken_);
69
+ address rewardToken_ = IAToken(aToken_).getIncentivesController().REWARD_TOKEN();
70
+ _rewardToken = IERC20Metadata(rewardToken_);
71
+ (address rewardAToken_, , ) = dataProvider.getReserveTokensAddresses(address(rewardToken_));
72
+ _rewardAToken = IAToken(rewardAToken_);
73
+ }
74
+
75
+ function initialize(
76
+ uint256 liquidityMin_,
77
+ uint256 liquidityMiddle_,
78
+ uint256 liquidityMax_,
79
+ uint256 claimRewardsMin_,
80
+ uint256 reinvestRewardsMin_,
81
+ uint256 maxSlippage_
82
+ ) public initializer {
83
+ __BaseAssetManager_init(liquidityMin_, liquidityMiddle_, liquidityMax_);
84
+ __AaveAssetManager_init(claimRewardsMin_, reinvestRewardsMin_, maxSlippage_);
85
+ }
86
+
87
+ // solhint-disable-next-line func-name-mixedcase
88
+ function __AaveAssetManager_init(
89
+ uint256 claimRewardsMin_,
90
+ uint256 reinvestRewardsMin_,
91
+ uint256 maxSlippage_
92
+ ) internal initializer {
93
+ _claimRewardsMin = claimRewardsMin_;
94
+ _reinvestRewardsMin = reinvestRewardsMin_;
95
+ require(maxSlippage_ <= 1e17, "maxSlippage can't be more than 10%");
96
+ _maxSlippage = maxSlippage_;
97
+ }
98
+
99
+ function getInvestmentValue() public view override returns (uint256) {
100
+ uint256 balance = aToken().balanceOf(address(this));
101
+ uint256 rewardBalance = rewardToken().balanceOf(address(this)) +
102
+ rewardAToken().balanceOf(address(this));
103
+ // Don't count unclaimedRewards as part of investmentValue to save gas and because if doing that will
104
+ // also need to claim rewards as part of _deinvest process
105
+ return balance + _rewardToCurrency(rewardBalance);
106
+ }
107
+
108
+ function unclaimedRewards() public view returns (uint256) {
109
+ // Also add unclaimed rewards
110
+ IAToken atk = aToken();
111
+ address[] memory atks = new address[](2);
112
+ atks[0] = address(atk);
113
+ atks[1] = address(rewardAToken());
114
+ return atk.getIncentivesController().getRewardsBalance(atks, address(this));
115
+ }
116
+
117
+ function _claimRewards(bool ignoreMin) internal returns (uint256) {
118
+ if (ignoreMin || unclaimedRewards() > _claimRewardsMin) {
119
+ IAToken atk = aToken();
120
+ address[] memory atks = new address[](2);
121
+ atks[0] = address(atk);
122
+ atks[1] = address(rewardAToken());
123
+ return atk.getIncentivesController().claimRewards(atks, type(uint256).max, address(this));
124
+ } else {
125
+ return 0;
126
+ }
127
+ }
128
+
129
+ function lendingPool() public view returns (ILendingPool) {
130
+ return ILendingPool(_aaveAddrProv.getLendingPool());
131
+ }
132
+
133
+ function priceOracle() public view returns (IPriceOracle) {
134
+ return IPriceOracle(_aaveAddrProv.getPriceOracle());
135
+ }
136
+
137
+ function _aaveDataProvider() internal view returns (AaveProtocolDataProvider) {
138
+ return AaveProtocolDataProvider(_aaveAddrProv.getAddress(DATA_PROVIDER_ID));
139
+ }
140
+
141
+ function aToken() public view returns (IAToken) {
142
+ return _aToken;
143
+ }
144
+
145
+ function rewardToken() public view returns (IERC20Metadata) {
146
+ return _rewardToken;
147
+ }
148
+
149
+ function rewardAToken() public view returns (IAToken) {
150
+ return _rewardAToken;
151
+ }
152
+
153
+ function _exchangePath() internal view returns (address[] memory) {
154
+ address[] memory path = new address[](2);
155
+ path[0] = address(rewardToken());
156
+ path[1] = address(currency());
157
+ return path;
158
+ }
159
+
160
+ function _rewardToCurrency(uint256 amount) internal view returns (uint256) {
161
+ // TODO Check: this is safe? Or I should use IPriceOracle instead
162
+ IPriceOracle oracle = priceOracle();
163
+ IERC20Metadata from_ = rewardToken();
164
+ IERC20Metadata to_ = currency();
165
+ uint256 exchangeRate = oracle.getAssetPrice(address(from_)).wadDiv(
166
+ oracle.getAssetPrice(address(to_))
167
+ );
168
+ if (from_.decimals() > to_.decimals()) {
169
+ exchangeRate /= 10**(from_.decimals() - to_.decimals());
170
+ } else {
171
+ exchangeRate *= 10**(from_.decimals() - to_.decimals());
172
+ }
173
+ return amount.wadMul(exchangeRate);
174
+ }
175
+
176
+ function reinvestRewardToken() public {
177
+ ILendingPool lendingPool_ = lendingPool();
178
+ IERC20Metadata token = rewardToken();
179
+ uint256 rewardBalance = token.balanceOf(address(this));
180
+ if (rewardBalance <= _reinvestRewardsMin) return;
181
+ token.approve(address(lendingPool_), rewardBalance);
182
+ lendingPool_.deposit(address(token), rewardBalance, address(this), 0);
183
+ }
184
+
185
+ function _swapRewards(uint256 amount, address outAddr) internal returns (uint256, uint256) {
186
+ address[] memory path = _exchangePath();
187
+ uint256 swapIn = IERC20Metadata(path[0]).balanceOf(address(this));
188
+ if (swapIn < amount) {
189
+ uint256 toWithdraw = amount - swapIn;
190
+ if (rewardAToken().balanceOf(address(this)) < toWithdraw) {
191
+ toWithdraw = type(uint256).max; // if not enought withdraw all
192
+ }
193
+ swapIn += lendingPool().withdraw(path[0], toWithdraw, address(this));
194
+ } else {
195
+ swapIn = amount;
196
+ }
197
+ uint256 swapOutMin = _rewardToCurrency(swapIn).wadMul(1e18 - _maxSlippage);
198
+ IERC20Metadata(path[0]).approve(address(_swapRouter), swapIn);
199
+ uint256[] memory amounts = _swapRouter.swapExactTokensForTokens(
200
+ swapIn,
201
+ swapOutMin,
202
+ path,
203
+ outAddr,
204
+ block.timestamp
205
+ );
206
+ emit RewardSwapped(swapIn, amounts[1]);
207
+ return (swapIn, amounts[1]);
208
+ }
209
+
210
+ function swapRewards(uint256 amount)
211
+ external
212
+ onlyPoolRole(SWAP_REWARDS_ROLE)
213
+ returns (uint256, uint256)
214
+ {
215
+ (uint256 swapIn, uint256 swapOut) = _swapRewards(amount, address(this));
216
+ ILendingPool lendingPool_ = lendingPool();
217
+ IERC20Metadata token = currency();
218
+ token.approve(address(lendingPool_), swapOut);
219
+ lendingPool_.deposit(address(token), swapOut, address(this), 0);
220
+ return (swapIn, swapOut);
221
+ }
222
+
223
+ function rebalance() public virtual override whenNotPaused {
224
+ _claimRewards(false);
225
+ super.rebalance();
226
+ reinvestRewardToken();
227
+ }
228
+
229
+ function _invest(uint256 amount) internal override {
230
+ ILendingPool lendingPool_ = lendingPool();
231
+ IERC20Metadata token = currency();
232
+ token.safeTransferFrom(address(_policyPool), address(this), amount);
233
+ token.approve(address(lendingPool_), amount);
234
+ lendingPool_.deposit(address(token), amount, address(this), 0);
235
+ super._invest(amount);
236
+ }
237
+
238
+ function _deinvest(uint256 amount) internal override {
239
+ uint256 remainingAmount = amount;
240
+ uint256 toWithdraw = amount;
241
+ if (aToken().balanceOf(address(this)) < toWithdraw) {
242
+ toWithdraw = type(uint256).max;
243
+ }
244
+ remainingAmount -= lendingPool().withdraw(
245
+ address(currency()),
246
+ toWithdraw,
247
+ address(_policyPool) // Withdraw directly to _policyPool
248
+ );
249
+ if (remainingAmount > 0) {
250
+ // In this case, it's safe using getAmountsIn to compute how many rewards are needed to swap
251
+ // but then, when I do the swap I validate the slippage with the market price (given by AAVE's Oracle)
252
+ // is acceptable
253
+ uint256 requiredRewards = _swapRouter.getAmountsIn(remainingAmount, _exchangePath())[0];
254
+ (, uint256 currencyOut) = _swapRewards(requiredRewards, address(_policyPool));
255
+ if (currencyOut < remainingAmount) {
256
+ remainingAmount -= currencyOut;
257
+ } else {
258
+ remainingAmount = 0;
259
+ }
260
+ }
261
+ super._deinvest(amount - remainingAmount);
262
+ }
263
+
264
+ /**
265
+ * @dev Deinvest all the assets and return the cash back to the PolicyPool.
266
+ * Called from PolicyPool when new asset manager is assigned
267
+ */
268
+ function _liquidateAll() internal virtual override {
269
+ _claimRewards(true);
270
+ lendingPool().withdraw(
271
+ address(currency()),
272
+ type(uint256).max,
273
+ address(_policyPool) // Withdraw directly to _policyPool
274
+ );
275
+ // Withdraw all rewards
276
+ lendingPool().withdraw(address(_rewardToken), type(uint256).max, address(this));
277
+ _swapRewards(_rewardToken.balanceOf(address(this)), address(_policyPool));
278
+ }
279
+
280
+ // Contract parameters
281
+ function claimRewardsMin() external view returns (uint256) {
282
+ return _claimRewardsMin;
283
+ }
284
+
285
+ function reinvestRewardsMin() external view returns (uint256) {
286
+ return _reinvestRewardsMin;
287
+ }
288
+
289
+ function maxSlippage() external view returns (uint256) {
290
+ return _maxSlippage;
291
+ }
292
+
293
+ function setClaimRewardsMin(uint256 newValue) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
294
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
295
+ require(
296
+ !tweak || _isTweakWad(_claimRewardsMin, newValue, 3e26),
297
+ "Tweak exceeded: claimRewardsMin tweaks only up to 30%"
298
+ );
299
+ _claimRewardsMin = newValue;
300
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setClaimRewardsMin, newValue, tweak);
301
+ }
302
+
303
+ function setReinvestRewardsMin(uint256 newValue)
304
+ external
305
+ onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
306
+ {
307
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
308
+ require(
309
+ !tweak || _isTweakWad(_reinvestRewardsMin, newValue, 3e26),
310
+ "Tweak exceeded: reinvestRewardsMin tweaks only up to 30%"
311
+ );
312
+ _reinvestRewardsMin = newValue;
313
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setReinvestRewardsMin, newValue, tweak);
314
+ }
315
+
316
+ function setMaxSlippage(uint256 newValue) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
317
+ require(newValue <= 1e17, "maxSlippage can't be more than 10%");
318
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
319
+ require(
320
+ !tweak || _isTweakWad(_maxSlippage, newValue, 3e26),
321
+ "Tweak exceeded: maxSlippage tweaks only up to 30%"
322
+ );
323
+ _maxSlippage = newValue;
324
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setMaxSlippage, newValue, tweak);
325
+ }
326
+ }
@@ -0,0 +1,283 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {WadRayMath} from "./WadRayMath.sol";
5
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
7
+ import {PolicyPoolComponent} from "./PolicyPoolComponent.sol";
8
+ import {IAssetManager} from "../interfaces/IAssetManager.sol";
9
+ import {IEToken} from "../interfaces/IEToken.sol";
10
+ import {IPolicyPoolConfig} from "../interfaces/IPolicyPoolConfig.sol";
11
+ import {Policy} from "./Policy.sol";
12
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
13
+
14
+ /**
15
+ * @title Ensuro Asset Manager base contract
16
+ * @dev Base class for asset managers that reinvest the assets of the protocol to get additional returns.
17
+ * The specific asset management strategy needs to be implemented by child contracts.
18
+ * Settings liquidityMin, liquidityMiddle, liquidityMax are the thresholds used to define how much liquidity
19
+ * to keep in the PolicyPool and when to invest/deinvest. Every invest/deinvest operation tries to leave the
20
+ * PolicyPool at liquidityMiddle.
21
+ * @custom:security-contact security@ensuro.co
22
+ * @author Ensuro
23
+ */
24
+ abstract contract BaseAssetManager is IAssetManager, PolicyPoolComponent {
25
+ using WadRayMath for uint256;
26
+
27
+ uint256 internal _liquidityMin;
28
+ uint256 internal _liquidityMiddle;
29
+ uint256 internal _liquidityMax;
30
+
31
+ uint256 internal _lastInvestmentValue;
32
+
33
+ event MoneyInvested(uint256 amount);
34
+ event MoneyDeinvested(uint256 amount);
35
+ event EarningsDistributed(bool positive, uint256 amount);
36
+
37
+ modifier validateParamsAfterChange() {
38
+ _;
39
+ _validateParameters();
40
+ }
41
+
42
+ /// @custom:oz-upgrades-unsafe-allow constructor
43
+ // solhint-disable-next-line no-empty-blocks
44
+ constructor(IPolicyPool policyPool_) PolicyPoolComponent(policyPool_) {}
45
+
46
+ /**
47
+ * @dev Initializes the asset manager
48
+ * @param liquidityMin_ Minimal liquidity to keep in pool's wallet
49
+ * @param liquidityMiddle_ Target liquidity when doing rebalance
50
+ * @param liquidityMax_ Maximum liquidity to keep in pool's wallet
51
+ */
52
+ // solhint-disable-next-line func-name-mixedcase
53
+ function __BaseAssetManager_init(
54
+ uint256 liquidityMin_,
55
+ uint256 liquidityMiddle_,
56
+ uint256 liquidityMax_
57
+ ) internal initializer {
58
+ __PolicyPoolComponent_init();
59
+ __BaseAssetManager_init_unchained(liquidityMin_, liquidityMiddle_, liquidityMax_);
60
+ }
61
+
62
+ // solhint-disable-next-line func-name-mixedcase
63
+ function __BaseAssetManager_init_unchained(
64
+ uint256 liquidityMin_,
65
+ uint256 liquidityMiddle_,
66
+ uint256 liquidityMax_
67
+ ) internal initializer {
68
+ /*
69
+ _lastInvestmentValue = 0;
70
+ */
71
+ _liquidityMin = liquidityMin_;
72
+ _liquidityMiddle = liquidityMiddle_;
73
+ _liquidityMax = liquidityMax_;
74
+ _validateParameters();
75
+ }
76
+
77
+ function _validateParameters() internal view {
78
+ require(
79
+ _liquidityMin <= _liquidityMiddle && _liquidityMiddle <= _liquidityMax,
80
+ "Validation: Liquidity limits are invalid"
81
+ );
82
+ }
83
+
84
+ /**
85
+ * @dev Returns the total amount that is available to invest by the asset manager
86
+ */
87
+ function totalInvestable() public view returns (uint256) {
88
+ uint256 poolInvestable = _policyPool.getInvestable();
89
+ uint256 etksInvestable = 0;
90
+ for (uint256 i = 0; i < _policyPool.getETokenCount(); i++) {
91
+ IEToken etk = _policyPool.getETokenAt(i);
92
+ etksInvestable += etk.getInvestable();
93
+ }
94
+ return poolInvestable + etksInvestable;
95
+ }
96
+
97
+ /**
98
+ * @dev Calculates asset earnings and distributes them updating accounting in PolicyPool and eTokens
99
+ */
100
+ function distributeEarnings() public virtual whenNotPaused {
101
+ // TODO Check: Anyone can call this funcion. This could be a potencial surface of flash loan attack?
102
+ _distributeEarnings(getInvestmentValue());
103
+ }
104
+
105
+ function _distributeEarnings(uint256 investmentValue) internal virtual whenNotPaused {
106
+ bool positive;
107
+ uint256 earnings;
108
+ if (investmentValue > _lastInvestmentValue) {
109
+ earnings = investmentValue - _lastInvestmentValue;
110
+ positive = true;
111
+ } else if (investmentValue < _lastInvestmentValue) {
112
+ earnings = _lastInvestmentValue - investmentValue;
113
+ positive = false;
114
+ } else {
115
+ return; // No earnings
116
+ }
117
+
118
+ uint256 totalInv = totalInvestable();
119
+
120
+ uint256 remaining = earnings;
121
+ for (uint256 i = 0; i < _policyPool.getETokenCount(); i++) {
122
+ IEToken etk = _policyPool.getETokenAt(i);
123
+ uint256 aux = earnings.wadMul(etk.getInvestable().wadDiv(totalInv));
124
+ if (aux > 0) {
125
+ etk.assetEarnings(aux, positive);
126
+ remaining -= aux;
127
+ }
128
+ }
129
+
130
+ if (remaining > 0) _policyPool.assetEarnings(remaining, positive);
131
+
132
+ _lastInvestmentValue = investmentValue;
133
+ emit EarningsDistributed(positive, earnings);
134
+ }
135
+
136
+ /**
137
+ * @dev Returns the current value of the investment portfolio
138
+ */
139
+ function getInvestmentValue() public view virtual returns (uint256);
140
+
141
+ /**
142
+ * @dev Rebalances cash between PolicyPool wallet and
143
+ */
144
+ function rebalance() public virtual whenNotPaused {
145
+ // TODO Check: Anyone can call this funcion. This could be a potencial surface of flash loan attack?
146
+ uint256 poolCash = currency().balanceOf(address(_policyPool));
147
+ if (poolCash > _liquidityMax) {
148
+ _invest(poolCash - _liquidityMiddle);
149
+ } else if (poolCash < _liquidityMin) {
150
+ uint256 deinvestAmount = Math.min(getInvestmentValue(), _liquidityMiddle - poolCash);
151
+ if (deinvestAmount > 0) {
152
+ _deinvest(deinvestAmount);
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @dev Function to be called automatically by a crontask - Distributes and rebalances
159
+ */
160
+ function checkpoint() external {
161
+ distributeEarnings();
162
+ rebalance();
163
+ }
164
+
165
+ /**
166
+ * @dev This is called from PolicyPool when doesn't have enought money for payment.
167
+ * After the call, there should be enought money in PolicyPool.currency().balanceOf(this) to
168
+ * do the payment
169
+ * @param paymentAmount The amount of the payment
170
+ */
171
+ function refillWallet(uint256 paymentAmount) external override onlyPolicyPool {
172
+ uint256 poolCash = currency().balanceOf(address(_policyPool));
173
+ require(poolCash < paymentAmount, "No need to refill the wallet for this payment");
174
+ uint256 investmentValue = getInvestmentValue();
175
+ // try to leave the pool balance at liquidity_middle after the payment
176
+ uint256 deinvest = paymentAmount + _liquidityMiddle - poolCash;
177
+ if (deinvest > investmentValue) deinvest = investmentValue;
178
+ _deinvest(deinvest);
179
+ }
180
+
181
+ function _invest(uint256 amount) internal virtual {
182
+ _lastInvestmentValue += amount;
183
+ emit MoneyInvested(amount);
184
+ // must be reimplemented do the actual cash movement
185
+ }
186
+
187
+ function _deinvest(uint256 amount) internal virtual {
188
+ _lastInvestmentValue -= Math.min(_lastInvestmentValue, amount);
189
+ emit MoneyDeinvested(amount);
190
+ // must be reimplemented do the actual cash movement
191
+ }
192
+
193
+ /**
194
+ * @dev Deinvest all the assets and return the cash back to the PolicyPool.
195
+ * Called from PolicyPool when new asset manager is assigned
196
+ */
197
+ function deinvestAll() external virtual override onlyPolicyPool {
198
+ uint256 poolBalanceBefore = currency().balanceOf(address(_policyPool));
199
+ _liquidateAll();
200
+ uint256 poolBalanceAfter = currency().balanceOf(address(_policyPool));
201
+ _distributeEarnings(poolBalanceAfter - poolBalanceBefore);
202
+ _lastInvestmentValue = 0;
203
+ emit MoneyDeinvested(poolBalanceAfter - poolBalanceBefore);
204
+ }
205
+
206
+ function _liquidateAll() internal virtual;
207
+
208
+ function liquidityMin() external view returns (uint256) {
209
+ return _liquidityMin;
210
+ }
211
+
212
+ function liquidityMiddle() external view returns (uint256) {
213
+ return _liquidityMiddle;
214
+ }
215
+
216
+ function liquidityMax() external view returns (uint256) {
217
+ return _liquidityMax;
218
+ }
219
+
220
+ function setLiquidityMin(uint256 newValue)
221
+ external
222
+ onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
223
+ validateParamsAfterChange
224
+ {
225
+ _setLiquidityMin(newValue);
226
+ }
227
+
228
+ function setLiquidityMiddle(uint256 newValue)
229
+ external
230
+ onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
231
+ validateParamsAfterChange
232
+ {
233
+ _setLiquidityMiddle(newValue);
234
+ }
235
+
236
+ function setLiquidityMax(uint256 newValue)
237
+ external
238
+ onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
239
+ validateParamsAfterChange
240
+ {
241
+ _setLiquidityMax(newValue);
242
+ }
243
+
244
+ function _setLiquidityMin(uint256 newValue) internal {
245
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
246
+ require(
247
+ !tweak || _isTweakRay(_liquidityMin, newValue, 3e26),
248
+ "Tweak exceeded: liquidityMin tweaks only up to 30%"
249
+ );
250
+ _liquidityMin = newValue;
251
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setLiquidityMin, newValue, tweak);
252
+ }
253
+
254
+ function _setLiquidityMiddle(uint256 newValue) internal {
255
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
256
+ require(
257
+ !tweak || _isTweakRay(_liquidityMiddle, newValue, 3e26),
258
+ "Tweak exceeded: liquidityMiddle tweaks only up to 30%"
259
+ );
260
+ _liquidityMiddle = newValue;
261
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setLiquidityMiddle, newValue, tweak);
262
+ }
263
+
264
+ function _setLiquidityMax(uint256 newValue) internal {
265
+ bool tweak = !hasPoolRole(LEVEL2_ROLE);
266
+ require(
267
+ !tweak || _isTweakRay(_liquidityMax, newValue, 3e26),
268
+ "Tweak exceeded: liquidityMax tweaks only up to 30%"
269
+ );
270
+ _liquidityMax = newValue;
271
+ _parameterChanged(IPolicyPoolConfig.GovernanceActions.setLiquidityMax, newValue, tweak);
272
+ }
273
+
274
+ function setLiquidityMultiple(
275
+ uint256 min,
276
+ uint256 middle,
277
+ uint256 max
278
+ ) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) validateParamsAfterChange {
279
+ if (min != type(uint256).max) _setLiquidityMin(min);
280
+ if (middle != type(uint256).max) _setLiquidityMiddle(middle);
281
+ if (max != type(uint256).max) _setLiquidityMax(max);
282
+ }
283
+ }