@ensuro/core 2.0.0-beta6 → 2.0.0-beta8

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 (44) hide show
  1. package/build/contracts/AccessManager.sol/AccessManager.json +74 -2
  2. package/build/contracts/ERC4626AssetManager.sol/ERC4626AssetManager.json +213 -0
  3. package/build/contracts/EToken.sol/EToken.json +114 -45
  4. package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +2 -2
  5. package/build/contracts/LiquidityThresholdAssetManager.sol/LiquidityThresholdAssetManager.json +197 -0
  6. package/build/contracts/Policy.sol/Policy.json +2 -2
  7. package/build/contracts/PolicyPool.sol/PolicyPool.json +324 -100
  8. package/build/contracts/PremiumsAccount.sol/PremiumsAccount.json +122 -2
  9. package/build/contracts/Reserve.sol/Reserve.json +71 -0
  10. package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +3 -3
  11. package/build/contracts/interfaces/IAccessManager.sol/IAccessManager.json +96 -0
  12. package/build/contracts/interfaces/IAssetManager.sol/IAssetManager.json +103 -0
  13. package/build/contracts/interfaces/IEToken.sol/IEToken.json +13 -0
  14. package/build/contracts/interfaces/IPolicyPool.sol/IPolicyPool.json +6 -14
  15. package/build/contracts/interfaces/IPremiumsAccount.sol/IPremiumsAccount.json +13 -0
  16. package/build/contracts/mocks/FixedRateVault.sol/FixedRateVault.json +736 -0
  17. package/build/contracts/mocks/PolicyPoolComponentMock.sol/PolicyPoolComponentMock.json +2 -2
  18. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMock.json +7 -15
  19. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMockForward.json +2 -2
  20. package/build/contracts/mocks/RiskModuleMock.sol/RiskModuleMock.json +674 -0
  21. package/contracts/AccessManager.sol +39 -8
  22. package/contracts/ERC4626AssetManager.sol +49 -0
  23. package/contracts/EToken.sol +72 -52
  24. package/contracts/LiquidityThresholdAssetManager.sol +159 -0
  25. package/contracts/PolicyPool.sol +155 -135
  26. package/contracts/PolicyPoolComponent.sol +29 -19
  27. package/contracts/PremiumsAccount.sol +125 -62
  28. package/contracts/Reserve.sol +86 -2
  29. package/contracts/RiskModule.sol +17 -6
  30. package/contracts/TrustfulRiskModule.sol +17 -2
  31. package/contracts/interfaces/IAccessManager.sol +23 -5
  32. package/contracts/interfaces/IAssetManager.sol +22 -0
  33. package/contracts/interfaces/IEToken.sol +21 -1
  34. package/contracts/interfaces/IPolicyPool.sol +6 -11
  35. package/contracts/interfaces/IPremiumsAccount.sol +5 -0
  36. package/contracts/mocks/FixedRateVault.sol +85 -0
  37. package/contracts/mocks/PolicyPoolMock.sol +2 -5
  38. package/contracts/mocks/RiskModuleMock.sol +69 -0
  39. package/js/test-utils.js +35 -26
  40. package/package.json +1 -1
  41. package/build/contracts/PolicyNFT.sol/PolicyNFT.json +0 -583
  42. package/build/contracts/interfaces/IPolicyNFT.sol/IPolicyNFT.json +0 -321
  43. package/contracts/PolicyNFT.sol +0 -96
  44. package/contracts/interfaces/IPolicyNFT.sol +0 -15
@@ -38,24 +38,47 @@ contract AccessManager is Initializable, AccessControlUpgradeable, UUPSUpgradeab
38
38
  // solhint-disable-next-line no-empty-blocks
39
39
  function _authorizeUpgrade(address) internal override onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {}
40
40
 
41
- function getComponentRole(address component, bytes32 role) public pure returns (bytes32) {
41
+ function getComponentRole(address component, bytes32 role)
42
+ public
43
+ pure
44
+ override
45
+ returns (bytes32)
46
+ {
42
47
  return bytes32(bytes20(component)) ^ role;
43
48
  }
44
49
 
50
+ function hasComponentRole(
51
+ address component,
52
+ bytes32 role,
53
+ address account,
54
+ bool alsoGlobal
55
+ ) public view override returns (bool) {
56
+ return
57
+ (alsoGlobal && hasRole(role, account)) || hasRole(getComponentRole(component, role), account);
58
+ }
59
+
45
60
  function checkComponentRole(
46
61
  address component,
47
62
  bytes32 role,
48
- address account
63
+ address account,
64
+ bool alsoGlobal
49
65
  ) external view override {
50
- _checkRole(getComponentRole(component, role), account);
66
+ if (!alsoGlobal || !hasRole(role, account)) {
67
+ _checkRole(getComponentRole(component, role), account);
68
+ }
51
69
  }
52
70
 
53
- function grantComponentRole(
71
+ function checkComponentRole2(
54
72
  address component,
55
- bytes32 role,
56
- address account
57
- ) external onlyRole(getRoleAdmin(getComponentRole(component, role))) {
58
- _grantRole(getComponentRole(component, role), account);
73
+ bytes32 role1,
74
+ bytes32 role2,
75
+ address account,
76
+ bool alsoGlobal
77
+ ) external view override {
78
+ if (alsoGlobal && hasRole(role1, account)) return;
79
+ if (hasRole(getComponentRole(component, role1), account)) return;
80
+ if (alsoGlobal && hasRole(role2, account)) return;
81
+ _checkRole(getComponentRole(component, role2), account);
59
82
  }
60
83
 
61
84
  function checkRole(bytes32 role, address account) external view override {
@@ -69,4 +92,12 @@ contract AccessManager is Initializable, AccessControlUpgradeable, UUPSUpgradeab
69
92
  ) external view override {
70
93
  if (!hasRole(role1, account)) _checkRole(role2, account);
71
94
  }
95
+
96
+ function grantComponentRole(
97
+ address component,
98
+ bytes32 role,
99
+ address account
100
+ ) external onlyRole(getRoleAdmin(getComponentRole(component, role))) {
101
+ _grantRole(getComponentRole(component, role), account);
102
+ }
72
103
  }
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {LiquidityThresholdAssetManager} from "./LiquidityThresholdAssetManager.sol";
5
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6
+ import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
7
+
8
+ /**
9
+ * @title Asset Manager that deploys the funds into an ERC4626 vault
10
+ * @dev Using liquidity thresholds defined in {LiquidityThresholdAssetManager}, deploys the funds into an ERC4626 vault.
11
+ * @custom:security-contact security@ensuro.co
12
+ * @author Ensuro
13
+ */
14
+ contract ERC4626AssetManager is LiquidityThresholdAssetManager {
15
+ IERC4626 internal immutable _vault;
16
+
17
+ constructor(IERC20Metadata asset_, IERC4626 vault_) LiquidityThresholdAssetManager(asset_) {
18
+ _vault = vault_;
19
+ }
20
+
21
+ function connect() public override {
22
+ super.connect();
23
+ _asset.approve(address(_vault), type(uint256).max); // infinite approval to the vault
24
+ }
25
+
26
+ function _invest(uint256 amount) internal override {
27
+ super._invest(amount);
28
+ _vault.deposit(amount, address(this));
29
+ }
30
+
31
+ function _deinvest(uint256 amount) internal override {
32
+ super._deinvest(amount);
33
+ _vault.withdraw(amount, address(this), address(this));
34
+ }
35
+
36
+ function deinvestAll() external override returns (int256 earnings) {
37
+ DiamondStorage storage ds = diamondStorage();
38
+ uint256 assets = _vault.redeem(_vault.balanceOf(address(this)), address(this), address(this));
39
+ earnings = int256(assets) - int256(uint256(ds.lastInvestmentValue));
40
+ ds.lastInvestmentValue = uint128(assets);
41
+ emit MoneyDeinvested(assets);
42
+ emit EarningsRecorded(earnings);
43
+ return earnings;
44
+ }
45
+
46
+ function getInvestmentValue() public view override returns (uint256) {
47
+ return _vault.convertToAssets(_vault.balanceOf(address(this)));
48
+ }
49
+ }
@@ -9,6 +9,7 @@ import {IEToken} from "./interfaces/IEToken.sol";
9
9
  import {IAccessManager} from "./interfaces/IAccessManager.sol";
10
10
  import {IPolicyPoolComponent} from "./interfaces/IPolicyPoolComponent.sol";
11
11
  import {ILPWhitelist} from "./interfaces/ILPWhitelist.sol";
12
+ import {IAssetManager} from "./interfaces/IAssetManager.sol";
12
13
  import {WadRayMath} from "./WadRayMath.sol";
13
14
  import {TimeScaled} from "./TimeScaled.sol";
14
15
 
@@ -59,9 +60,12 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
59
60
 
60
61
  PackedParams internal _params;
61
62
 
63
+ IAssetManager internal _assetManager;
64
+
62
65
  event InternalLoan(address indexed borrower, uint256 value, uint256 amountAsked);
63
66
  event InternalLoanRepaid(address indexed borrower, uint256 value);
64
- event PoolBorrowerAdded(address borrower);
67
+ event InternalBorrowerAdded(address indexed borrower);
68
+ event InternalBorrowerRemoved(address indexed borrower, uint256 defaultedDebt);
65
69
 
66
70
  modifier onlyBorrower() {
67
71
  require(_loans[_msgSender()].scale != 0, "The caller must be a borrower");
@@ -407,7 +411,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
407
411
  address from,
408
412
  address to,
409
413
  uint256 amount
410
- ) internal virtual {
414
+ ) internal virtual whenNotPaused {
411
415
  require(
412
416
  from == address(0) ||
413
417
  to == address(0) ||
@@ -447,6 +451,14 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
447
451
  else return 0;
448
452
  }
449
453
 
454
+ function assetManager() public view override returns (IAssetManager) {
455
+ return _assetManager;
456
+ }
457
+
458
+ function _setAssetManager(IAssetManager newAM) internal override {
459
+ _assetManager = newAM;
460
+ }
461
+
450
462
  function scr() public view virtual override returns (uint256) {
451
463
  return uint256(_scr.scr);
452
464
  }
@@ -475,7 +487,12 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
475
487
  return uint256(_scr.scr).wadDiv(this.totalSupply());
476
488
  }
477
489
 
478
- function lockScr(uint256 scrAmount, uint256 policyInterestRate) external override onlyBorrower {
490
+ function lockScr(uint256 scrAmount, uint256 policyInterestRate)
491
+ external
492
+ override
493
+ onlyBorrower
494
+ whenNotPaused
495
+ {
479
496
  require(
480
497
  scrAmount <= this.fundsAvailableToLock(),
481
498
  "Not enought funds available to cover the SCR"
@@ -523,6 +540,10 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
523
540
  _discreteChange(adjustment);
524
541
  }
525
542
 
543
+ function _assetEarnings(int256 earnings) internal override {
544
+ _discreteChange(earnings);
545
+ }
546
+
526
547
  function _discreteChange(int256 amount) internal {
527
548
  _tsScaled.discreteChange(amount, tokenInterestRate());
528
549
  _updateTokenInterestRate();
@@ -532,7 +553,6 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
532
553
  external
533
554
  override
534
555
  onlyPolicyPool
535
- whenNotPaused
536
556
  returns (uint256)
537
557
  {
538
558
  require(
@@ -557,7 +577,6 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
557
577
  external
558
578
  override
559
579
  onlyPolicyPool
560
- whenNotPaused
561
580
  returns (uint256)
562
581
  {
563
582
  uint256 balance = balanceOf(provider);
@@ -576,10 +595,16 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
576
595
  TimeScaled.ScaledAmount storage loan = _loans[borrower];
577
596
  if (loan.scale == 0) {
578
597
  loan.init();
579
- emit PoolBorrowerAdded(borrower);
598
+ emit InternalBorrowerAdded(borrower);
580
599
  }
581
600
  }
582
601
 
602
+ function removeBorrower(address borrower) external override onlyPolicyPool {
603
+ uint256 defaultedDebt = getLoan(borrower);
604
+ delete _loans[borrower];
605
+ emit InternalBorrowerRemoved(borrower, defaultedDebt);
606
+ }
607
+
583
608
  function internalLoan(
584
609
  uint256 amount,
585
610
  address receiver,
@@ -620,59 +645,54 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
620
645
  return uint256(_params.internalLoanInterestRate) * 1e14; // to wad 4 -> 18 digits
621
646
  }
622
647
 
623
- function setInternalLoanInterestRate(uint256 newRate)
624
- external
625
- onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
626
- {
627
- bool tweak = !hasPoolRole(LEVEL2_ROLE);
628
- require(
629
- !tweak || _isTweakWad(internalLoanInterestRate(), newRate, 3e17),
630
- "Tweak exceeded: internalLoanInterestRate tweaks only up to 30%"
631
- );
632
- // This call changes the interest rate without updating the current loans up to this point
633
- // So, if interest rate goes from 5% to 6%, this change will be retroactive to the lastUpdate of each
634
- // loan. Since it's a permissioned call, I'm ok with this. If a caller wants to reduce the impact, it can
635
- // issue 1 wei repayLoan to each active loan, forcing the update of the scales
636
- _params.internalLoanInterestRate = uint16(newRate / 1e14);
637
- _parameterChanged(IAccessManager.GovernanceActions.setInternalLoanInterestRate, newRate, tweak);
638
- }
639
-
640
- function setLiquidityRequirement(uint256 newRate)
648
+ function setParam(Parameter param, uint256 newValue)
641
649
  external
642
- onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
650
+ onlyGlobalOrComponentRole2(LEVEL2_ROLE, LEVEL3_ROLE)
643
651
  {
644
652
  bool tweak = !hasPoolRole(LEVEL2_ROLE);
645
- require(
646
- !tweak || _isTweakWad(liquidityRequirement(), newRate, 1e17),
647
- "Tweak exceeded: liquidityRequirement tweaks only up to 10%"
648
- );
649
- _params.liquidityRequirement = uint16(newRate / 1e14);
650
- _parameterChanged(IAccessManager.GovernanceActions.setLiquidityRequirement, newRate, tweak);
651
- }
652
-
653
- function setMaxUtilizationRate(uint256 newRate) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
654
- bool tweak = !hasPoolRole(LEVEL2_ROLE);
655
- require(
656
- !tweak || _isTweakWad(maxUtilizationRate(), newRate, 3e17),
657
- "Tweak exceeded: maxUtilizationRate tweaks only up to 30%"
658
- );
659
- _params.maxUtilizationRate = uint16(newRate / 1e14);
660
- _parameterChanged(IAccessManager.GovernanceActions.setMaxUtilizationRate, newRate, tweak);
661
- }
662
-
663
- function setMinUtilizationRate(uint256 newRate) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
664
- bool tweak = !hasPoolRole(LEVEL2_ROLE);
665
- require(
666
- !tweak || _isTweakWad(minUtilizationRate(), newRate, 3e17),
667
- "Tweak exceeded: minUtilizationRate tweaks only up to 30%"
653
+ if (param == Parameter.liquidityRequirement) {
654
+ require(
655
+ !tweak || _isTweakWad(liquidityRequirement(), newValue, 1e17),
656
+ "Tweak exceeded: liquidityRequirement tweaks only up to 10%"
657
+ );
658
+ _params.liquidityRequirement = uint16(newValue / 1e14);
659
+ } else if (param == Parameter.minUtilizationRate) {
660
+ require(
661
+ !tweak || _isTweakWad(minUtilizationRate(), newValue, 3e17),
662
+ "Tweak exceeded: minUtilizationRate tweaks only up to 30%"
663
+ );
664
+ _params.minUtilizationRate = uint16(newValue / 1e14);
665
+ } else if (param == Parameter.maxUtilizationRate) {
666
+ require(
667
+ !tweak || _isTweakWad(maxUtilizationRate(), newValue, 3e17),
668
+ "Tweak exceeded: maxUtilizationRate tweaks only up to 30%"
669
+ );
670
+ _params.maxUtilizationRate = uint16(newValue / 1e14);
671
+ } else if (param == Parameter.internalLoanInterestRate) {
672
+ require(
673
+ !tweak || _isTweakWad(internalLoanInterestRate(), newValue, 3e17),
674
+ "Tweak exceeded: internalLoanInterestRate tweaks only up to 30%"
675
+ );
676
+ // This call changes the interest rate without updating the current loans up to this point
677
+ // So, if interest rate goes from 5% to 6%, this change will be retroactive to the lastUpdate of each
678
+ // loan. Since it's a permissioned call, I'm ok with this. If a caller wants to reduce the impact, it can
679
+ // issue 1 wei repayLoan to each active loan, forcing the update of the scales
680
+ _params.internalLoanInterestRate = uint16(newValue / 1e14);
681
+ } else {
682
+ revert("Invalid param!");
683
+ }
684
+ _parameterChanged(
685
+ IAccessManager.GovernanceActions(
686
+ uint256(IAccessManager.GovernanceActions.setLiquidityRequirement) + uint256(param)
687
+ ),
688
+ newValue,
689
+ tweak
668
690
  );
669
- _params.minUtilizationRate = uint16(newRate / 1e14);
670
- _parameterChanged(IAccessManager.GovernanceActions.setMinUtilizationRate, newRate, tweak);
671
691
  }
672
692
 
673
693
  function setWhitelist(ILPWhitelist lpWhitelist_)
674
694
  external
675
- onlyPoolRole2(GUARDIAN_ROLE, LEVEL1_ROLE)
695
+ onlyGlobalOrComponentRole2(GUARDIAN_ROLE, LEVEL1_ROLE)
676
696
  {
677
697
  require(
678
698
  address(lpWhitelist_) == address(0) ||
@@ -680,7 +700,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
680
700
  "Component not linked to this PolicyPool"
681
701
  );
682
702
  _params.whitelist = lpWhitelist_;
683
- emit ComponentChanged(IAccessManager.GovernanceActions.setLPWhitelist, address(lpWhitelist_));
703
+ _componentChanged(IAccessManager.GovernanceActions.setLPWhitelist, address(lpWhitelist_));
684
704
  }
685
705
 
686
706
  function whitelist() external view returns (ILPWhitelist) {
@@ -0,0 +1,159 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {WadRayMath} from "./WadRayMath.sol";
5
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6
+ import {IPolicyPoolComponent} from "./interfaces/IPolicyPoolComponent.sol";
7
+ import {IAssetManager} from "./interfaces/IAssetManager.sol";
8
+ import {IAccessManager} from "./interfaces/IAccessManager.sol";
9
+ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
10
+
11
+ /**
12
+ * @title Base class for asset management strategies that use thresholds for cash and investment balance
13
+ * @dev Base class for asset management strategies that use thresholds for cash and investment balance.
14
+ * The specific asset management strategy needs to be implemented by child contracts.
15
+ * Settings liquidityMin, liquidityMiddle, liquidityMax are the thresholds used to define how much liquidity
16
+ * to keep in the PolicyPool and when to invest/deinvest. Every invest/deinvest operation tries to leave the
17
+ * cash at liquidityMiddle.
18
+ * @custom:security-contact security@ensuro.co
19
+ * @author Ensuro
20
+ */
21
+ abstract contract LiquidityThresholdAssetManager is IAssetManager {
22
+ using WadRayMath for uint256;
23
+
24
+ IERC20Metadata internal immutable _asset;
25
+ event GovernanceAction(IAccessManager.GovernanceActions indexed action, uint256 value);
26
+
27
+ struct DiamondStorage {
28
+ uint32 liquidityMin; // stored with 0 decimals
29
+ uint32 liquidityMiddle; // stored with 0 decimals
30
+ uint32 liquidityMax; // stored with 0 decimals
31
+ uint128 lastInvestmentValue;
32
+ }
33
+
34
+ modifier validateParamsAfterChange() {
35
+ _;
36
+ _validateParameters();
37
+ }
38
+
39
+ constructor(IERC20Metadata asset_) {
40
+ _asset = asset_;
41
+ }
42
+
43
+ function diamondStorage() internal pure returns (DiamondStorage storage ds) {
44
+ // Inspired from https://eips.ethereum.org/EIPS/eip-2535#facets-state-variables-and-diamond-storage
45
+ // Set the position of our struct in contract storage
46
+ bytes32 storagePosition = keccak256("co.ensuro.LiquidityThresholdAssetManager");
47
+ // solhint-disable-next-line no-inline-assembly
48
+ assembly {
49
+ ds.slot := storagePosition
50
+ }
51
+ }
52
+
53
+ function _validateParameters() internal view {
54
+ DiamondStorage storage ds = diamondStorage();
55
+
56
+ require(
57
+ ds.liquidityMin <= ds.liquidityMiddle && ds.liquidityMiddle <= ds.liquidityMax,
58
+ "Validation: Liquidity limits are invalid"
59
+ );
60
+ }
61
+
62
+ function connect() public virtual override {
63
+ require(
64
+ IPolicyPoolComponent(address(this)).policyPool().currency() == _asset,
65
+ "Asset mismatch"
66
+ );
67
+ }
68
+
69
+ function recordEarnings() external virtual override returns (int256) {
70
+ uint256 investmentValue = getInvestmentValue();
71
+ DiamondStorage storage ds = diamondStorage();
72
+ int256 earnings = int256(investmentValue) - int256(uint256(ds.lastInvestmentValue));
73
+ ds.lastInvestmentValue = uint128(investmentValue);
74
+ emit EarningsRecorded(earnings);
75
+ return earnings;
76
+ }
77
+
78
+ /**
79
+ * @dev Returns the current value of the investment portfolio
80
+ */
81
+ function getInvestmentValue() public view virtual returns (uint256);
82
+
83
+ /**
84
+ * @dev Rebalances cash between PolicyPool wallet and
85
+ */
86
+ function rebalance() external virtual override {
87
+ uint256 cash = _asset.balanceOf(address(this));
88
+ if (cash > liquidityMax()) {
89
+ _invest(cash - liquidityMiddle());
90
+ } else if (cash < liquidityMin()) {
91
+ uint256 deinvestAmount = Math.min(getInvestmentValue(), liquidityMiddle() - cash);
92
+ if (deinvestAmount > 0) {
93
+ _deinvest(deinvestAmount);
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @dev This is called from PolicyPool when doesn't have enought money for payment.
100
+ * After the call, there should be enought money in PolicyPool.currency().balanceOf(this) to
101
+ * do the payment
102
+ * @param paymentAmount The amount of the payment
103
+ */
104
+ function refillWallet(uint256 paymentAmount) external override {
105
+ uint256 cash = _asset.balanceOf(address(this));
106
+ require(cash < paymentAmount, "No need to refill the wallet for this payment");
107
+ uint256 investmentValue = getInvestmentValue();
108
+ // try to leave the pool balance at liquidity_middle after the payment
109
+ uint256 deinvest = paymentAmount + liquidityMiddle() - cash;
110
+ if (deinvest > investmentValue) deinvest = investmentValue;
111
+ _deinvest(deinvest);
112
+ }
113
+
114
+ function _invest(uint256 amount) internal virtual {
115
+ DiamondStorage storage ds = diamondStorage();
116
+ ds.lastInvestmentValue += uint128(amount);
117
+ emit MoneyInvested(amount);
118
+ // must be reimplemented do the actual cash movement
119
+ }
120
+
121
+ function _deinvest(uint256 amount) internal virtual {
122
+ DiamondStorage storage ds = diamondStorage();
123
+ ds.lastInvestmentValue -= uint128(Math.min(uint256(ds.lastInvestmentValue), amount));
124
+ emit MoneyDeinvested(amount);
125
+ // must be reimplemented do the actual cash movement
126
+ }
127
+
128
+ function liquidityMin() public view returns (uint256) {
129
+ return diamondStorage().liquidityMin * 10**_asset.decimals();
130
+ }
131
+
132
+ function liquidityMiddle() public view returns (uint256) {
133
+ return diamondStorage().liquidityMiddle * 10**_asset.decimals();
134
+ }
135
+
136
+ function liquidityMax() public view returns (uint256) {
137
+ return diamondStorage().liquidityMax * 10**_asset.decimals();
138
+ }
139
+
140
+ function setLiquidityThresholds(
141
+ uint256 min,
142
+ uint256 middle,
143
+ uint256 max
144
+ ) external validateParamsAfterChange {
145
+ DiamondStorage storage ds = diamondStorage();
146
+ if (min != type(uint256).max) {
147
+ ds.liquidityMin = uint32(min / 10**_asset.decimals());
148
+ emit GovernanceAction(IAccessManager.GovernanceActions.setLiquidityMin, min);
149
+ }
150
+ if (middle != type(uint256).max) {
151
+ ds.liquidityMiddle = uint32(middle / 10**_asset.decimals());
152
+ emit GovernanceAction(IAccessManager.GovernanceActions.setLiquidityMiddle, middle);
153
+ }
154
+ if (max != type(uint256).max) {
155
+ ds.liquidityMax = uint32(max / 10**_asset.decimals());
156
+ emit GovernanceAction(IAccessManager.GovernanceActions.setLiquidityMax, max);
157
+ }
158
+ }
159
+ }