@ensuro/core 2.0.0-beta10 → 2.0.0-beta11

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 (49) hide show
  1. package/README.md +13 -0
  2. package/build/contracts/AccessManager.sol/AccessManager.json +7 -2
  3. package/build/contracts/ERC4626AssetManager.sol/ERC4626AssetManager.json +21 -2
  4. package/build/contracts/EToken.sol/EToken.json +21 -2
  5. package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +21 -2
  6. package/build/contracts/LiquidityThresholdAssetManager.sol/LiquidityThresholdAssetManager.json +19 -0
  7. package/build/contracts/Policy.sol/Policy.json +2 -2
  8. package/build/contracts/PolicyPool.sol/PolicyPool.json +2 -2
  9. package/build/contracts/PolicyPoolComponent.sol/PolicyPoolComponent.json +19 -0
  10. package/build/contracts/PremiumsAccount.sol/PremiumsAccount.json +21 -2
  11. package/build/contracts/Reserve.sol/Reserve.json +19 -0
  12. package/build/contracts/RiskModule.sol/RiskModule.json +19 -0
  13. package/build/contracts/SignedQuoteRiskModule.sol/SignedQuoteRiskModule.json +21 -2
  14. package/build/contracts/TimeScaled.sol/TimeScaled.json +2 -2
  15. package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +21 -2
  16. package/build/contracts/dependencies/WadRayMath.sol/WadRayMath.json +2 -2
  17. package/build/contracts/interfaces/IAssetManager.sol/IAssetManager.json +19 -0
  18. package/build/contracts/interfaces/IPolicyPoolComponent.sol/IPolicyPoolComponent.json +19 -0
  19. package/build/contracts/mocks/FixedRateVault.sol/FixedRateVault.json +2 -2
  20. package/build/contracts/mocks/ForwardProxy.sol/ForwardProxy.json +2 -2
  21. package/build/contracts/mocks/InterfaceIdCalculator.sol/InterfaceIdCalculator.json +180 -0
  22. package/build/contracts/mocks/PolicyHolderMock.sol/PolicyHolderMock.json +2 -2
  23. package/build/contracts/mocks/PolicyPoolComponentMock.sol/PolicyPoolComponentMock.json +21 -2
  24. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMock.json +2 -2
  25. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMockForward.json +2 -2
  26. package/build/contracts/mocks/RiskModuleMock.sol/RiskModuleMock.json +21 -2
  27. package/build/contracts/mocks/TestCurrency.sol/TestCurrency.json +2 -2
  28. package/build/contracts/mocks/TestNFT.sol/TestNFT.json +2 -2
  29. package/contracts/AccessManager.sol +30 -1
  30. package/contracts/ERC4626AssetManager.sol +3 -0
  31. package/contracts/EToken.sol +89 -19
  32. package/contracts/LPManualWhitelist.sol +18 -2
  33. package/contracts/LiquidityThresholdAssetManager.sol +17 -2
  34. package/contracts/Policy.sol +4 -2
  35. package/contracts/PolicyPool.sol +58 -11
  36. package/contracts/PolicyPoolComponent.sol +25 -3
  37. package/contracts/PremiumsAccount.sol +55 -23
  38. package/contracts/Reserve.sol +19 -3
  39. package/contracts/RiskModule.sol +37 -13
  40. package/contracts/SignedQuoteRiskModule.sol +7 -0
  41. package/contracts/TrustfulRiskModule.sol +7 -0
  42. package/contracts/interfaces/IAssetManager.sol +8 -3
  43. package/contracts/interfaces/IPolicyPool.sol +1 -1
  44. package/contracts/interfaces/IPolicyPoolComponent.sol +2 -1
  45. package/contracts/mocks/InterfaceIdCalculator.sol +32 -0
  46. package/contracts/mocks/PolicyPoolComponentMock.sol +10 -0
  47. package/js/deploy.js +704 -0
  48. package/js/test-utils.js +139 -37
  49. package/package.json +1 -1
@@ -9,6 +9,16 @@ import {IAccessManager} from "./interfaces/IAccessManager.sol";
9
9
  /**
10
10
  * @title AccessManager - Protocol access roles
11
11
  * @dev Contract that holds the access roles for PolicyPool and other components of the protocol.
12
+ *
13
+ * [CAUTION]
14
+ * ====
15
+ * Avoid leaving a this contract without DEFAULT_ADMIN_ROLE.
16
+ *
17
+ * This contract includes the methods `revokeRole()` and `renounceRole()` that allow to revoke or renounce to
18
+ * specific roles. Even when there are valid reasons to leave these methods (for example revoking the initial
19
+ * DEFAULT_ADMIN_ROLE of the deployer account to leave just the governance account), it's good to mention these
20
+ * methods have to be used with care, avoiding leaving the contract without any default admin.
21
+ *
12
22
  * @custom:security-contact security@ensuro.co
13
23
  * @author Ensuro
14
24
  */
@@ -24,6 +34,11 @@ contract AccessManager is Initializable, AccessControlUpgradeable, UUPSUpgradeab
24
34
  _;
25
35
  }
26
36
 
37
+ /// @custom:oz-upgrades-unsafe-allow constructor
38
+ constructor() {
39
+ _disableInitializers();
40
+ }
41
+
27
42
  function initialize() public initializer {
28
43
  __AccessControl_init();
29
44
  __UUPSUpgradeable_init();
@@ -31,13 +46,20 @@ contract AccessManager is Initializable, AccessControlUpgradeable, UUPSUpgradeab
31
46
  }
32
47
 
33
48
  // solhint-disable-next-line func-name-mixedcase
34
- function __AccessManager_init_unchained() internal initializer {
49
+ function __AccessManager_init_unchained() internal onlyInitializing {
35
50
  _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
36
51
  }
37
52
 
38
53
  // solhint-disable-next-line no-empty-blocks
39
54
  function _authorizeUpgrade(address) internal override onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {}
40
55
 
56
+ /**
57
+ * @dev See {IERC165-supportsInterface}.
58
+ */
59
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
60
+ return super.supportsInterface(interfaceId) || interfaceId == type(IAccessManager).interfaceId;
61
+ }
62
+
41
63
  function getComponentRole(address component, bytes32 role)
42
64
  public
43
65
  pure
@@ -100,4 +122,11 @@ contract AccessManager is Initializable, AccessControlUpgradeable, UUPSUpgradeab
100
122
  ) external onlyRole(getRoleAdmin(getComponentRole(component, role))) {
101
123
  _grantRole(getComponentRole(component, role), account);
102
124
  }
125
+
126
+ /**
127
+ * @dev This empty reserved space is put in place to allow future versions to add new
128
+ * variables without shifting down storage in the inheritance chain.
129
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
130
+ */
131
+ uint256[50] private __gap;
103
132
  }
@@ -11,6 +11,8 @@ import {LiquidityThresholdAssetManager} from "./LiquidityThresholdAssetManager.s
11
11
  * @dev Using liquidity thresholds defined in {LiquidityThresholdAssetManager}, deploys the funds into an ERC4626 vault.
12
12
  * @custom:security-contact security@ensuro.co
13
13
  * @author Ensuro
14
+ *
15
+ * @notice This contracts uses Diamond Storage and should not define state variables outside of that. See the diamondStorage method for more details.
14
16
  */
15
17
  contract ERC4626AssetManager is LiquidityThresholdAssetManager {
16
18
  using SafeCast for uint256;
@@ -18,6 +20,7 @@ contract ERC4626AssetManager is LiquidityThresholdAssetManager {
18
20
  IERC4626 internal immutable _vault;
19
21
 
20
22
  constructor(IERC20Metadata asset_, IERC4626 vault_) LiquidityThresholdAssetManager(asset_) {
23
+ require(address(vault_) != address(0), "ERC4626AssetManager: vault cannot be zero address");
21
24
  _vault = vault_;
22
25
  }
23
26
 
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  pragma solidity ^0.8.0;
3
3
 
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
4
5
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
6
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
7
  import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
@@ -31,6 +32,13 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
31
32
  using SafeERC20 for IERC20Metadata;
32
33
  using SafeCast for uint256;
33
34
 
35
+ uint256 internal constant FOUR_DECIMAL_TO_WAD = 1e14;
36
+ uint16 internal constant HUNDRED_PERCENT = 1e4;
37
+ uint16 internal constant MAX_UR_MIN = 5e3; // 50% - Minimum value for Max Utilization Rate
38
+ uint16 internal constant LIQ_REQ_MIN = 8e3; // 80%
39
+ uint16 internal constant LIQ_REQ_MAX = 13e3; // 130%
40
+ uint16 internal constant INT_LOAN_IR_MAX = 5e3; // 50% - Maximum value for InternalLoan interest rate
41
+
34
42
  // Attributes taken from ERC20
35
43
  mapping(address => uint256) private _balances;
36
44
  mapping(address => mapping(address => uint256)) private _allowances;
@@ -90,7 +98,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
90
98
  uint256 maxUtilizationRate_,
91
99
  uint256 internalLoanInterestRate_
92
100
  ) public initializer {
93
- __PolicyPoolComponent_init();
101
+ __Reserve_init();
94
102
  __EToken_init_unchained(name_, symbol_, maxUtilizationRate_, internalLoanInterestRate_);
95
103
  }
96
104
 
@@ -100,7 +108,9 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
100
108
  string memory symbol_,
101
109
  uint256 maxUtilizationRate_,
102
110
  uint256 internalLoanInterestRate_
103
- ) internal initializer {
111
+ ) internal onlyInitializing {
112
+ require(bytes(name_).length > 0, "EToken: name cannot be empty");
113
+ require(bytes(symbol_).length > 0, "EToken: symbol cannot be empty");
104
114
  _name = name_;
105
115
  _symbol = symbol_;
106
116
  _tsScaled.init();
@@ -111,7 +121,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
111
121
  }); */
112
122
  _params = PackedParams({
113
123
  maxUtilizationRate: _wadTo4(maxUtilizationRate_),
114
- liquidityRequirement: 1e4,
124
+ liquidityRequirement: HUNDRED_PERCENT,
115
125
  minUtilizationRate: 0,
116
126
  internalLoanInterestRate: _wadTo4(internalLoanInterestRate_),
117
127
  whitelist: ILPWhitelist(address(0))
@@ -120,19 +130,42 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
120
130
  _validateParameters();
121
131
  }
122
132
 
133
+ /**
134
+ * @dev See {IERC165-supportsInterface}.
135
+ */
136
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
137
+ return
138
+ super.supportsInterface(interfaceId) ||
139
+ interfaceId == type(IERC20).interfaceId ||
140
+ interfaceId == type(IERC20Metadata).interfaceId ||
141
+ interfaceId == type(IEToken).interfaceId;
142
+ }
143
+
123
144
  // runs validation on EToken parameters
124
145
  function _validateParameters() internal view override {
125
146
  require(
126
- _params.liquidityRequirement >= 8e3 && _params.liquidityRequirement <= 13e3,
147
+ _params.liquidityRequirement >= LIQ_REQ_MIN && _params.liquidityRequirement <= LIQ_REQ_MAX,
127
148
  "Validation: liquidityRequirement must be [0.8, 1.3]"
128
149
  );
129
150
  require(
130
- _params.maxUtilizationRate >= 5e3 && _params.maxUtilizationRate <= 1e4,
151
+ _params.maxUtilizationRate >= MAX_UR_MIN && _params.maxUtilizationRate <= HUNDRED_PERCENT,
131
152
  "Validation: maxUtilizationRate must be [0.5, 1]"
132
153
  );
133
- require(_params.minUtilizationRate <= 1e4, "Validation: minUtilizationRate must be [0, 1]");
134
154
  require(
135
- _params.internalLoanInterestRate <= 5e3,
155
+ _params.minUtilizationRate <= HUNDRED_PERCENT,
156
+ "Validation: minUtilizationRate must be [0, 1]"
157
+ );
158
+ /*
159
+ * We don't validate minUtilizationRate < maxUtilizationRate because the opposite is valid too.
160
+ * These limits aren't strong limits on the values the utilization rate can take, but instead they are
161
+ * limits on specific operations.
162
+ * `minUtilizationRate` is used to avoid new deposits to dilute the yields of existing LPs, but it doesn't
163
+ * prevent the UR from going down in other operations (`unlockScr` for example).
164
+ * `maxUtilizationRate` is used to prevent selling more coverage when UR is too high, only checked on `lockScr`
165
+ * operations, but not in withdrawals or other operations.
166
+ */
167
+ require(
168
+ _params.internalLoanInterestRate <= INT_LOAN_IR_MAX,
136
169
  "Validation: internalLoanInterestRate must be <= 50%"
137
170
  );
138
171
  }
@@ -243,15 +276,34 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
243
276
  address recipient,
244
277
  uint256 amount
245
278
  ) public virtual override returns (bool) {
279
+ address spender = _msgSender();
280
+ _spendAllowance(sender, spender, amount);
246
281
  _transfer(sender, recipient, amount);
247
-
248
- uint256 currentAllowance = _allowances[sender][_msgSender()];
249
- require(currentAllowance >= amount, "EToken: transfer amount exceeds allowance");
250
- _approve(sender, _msgSender(), currentAllowance - amount);
251
-
252
282
  return true;
253
283
  }
254
284
 
285
+ /**
286
+ * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
287
+ *
288
+ * Does not update the allowance amount in case of infinite allowance.
289
+ * Revert if not enough allowance is available.
290
+ *
291
+ * Might emit an {Approval} event.
292
+ */
293
+ function _spendAllowance(
294
+ address owner,
295
+ address spender,
296
+ uint256 amount
297
+ ) internal virtual {
298
+ uint256 currentAllowance = allowance(owner, spender);
299
+ if (currentAllowance != type(uint256).max) {
300
+ require(currentAllowance >= amount, "EToken: insufficient allowance");
301
+ unchecked {
302
+ _approve(owner, spender, currentAllowance - amount);
303
+ }
304
+ }
305
+ }
306
+
255
307
  /**
256
308
  * @dev Atomically increases the allowance granted to `spender` by the caller.
257
309
  *
@@ -265,7 +317,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
265
317
  * - `spender` cannot be the zero address.
266
318
  */
267
319
  function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
268
- _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
320
+ _approve(_msgSender(), spender, allowance(_msgSender(), spender) + addedValue);
269
321
  return true;
270
322
  }
271
323
 
@@ -339,6 +391,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
339
391
  */
340
392
  function _mint(address account, uint256 amount) internal virtual {
341
393
  require(account != address(0), "EToken: mint to the zero address");
394
+ require(amount > 0, "EToken: amount to mint should be greater than zero");
342
395
 
343
396
  _beforeTokenTransfer(address(0), account, amount);
344
397
  uint256 scaledAmount = _tsScaled.add(amount, tokenInterestRate());
@@ -464,12 +517,12 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
464
517
  // solhint-disable-next-line func-name-mixedcase
465
518
  function _4toWad(uint16 value) internal pure returns (uint256) {
466
519
  // 4 decimals to Wad (18 decimals)
467
- return uint256(value) * 1e14;
520
+ return uint256(value) * FOUR_DECIMAL_TO_WAD;
468
521
  }
469
522
 
470
523
  function _wadTo4(uint256 value) internal pure returns (uint16) {
471
524
  // Wad to 4 decimals
472
- return (value / 1e14).toUint16();
525
+ return (value / FOUR_DECIMAL_TO_WAD).toUint16();
473
526
  }
474
527
 
475
528
  function scr() public view virtual override returns (uint256) {
@@ -508,7 +561,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
508
561
  {
509
562
  require(
510
563
  scrAmount <= this.fundsAvailableToLock(),
511
- "Not enought funds available to cover the SCR"
564
+ "Not enough funds available to cover the SCR"
512
565
  );
513
566
  _tsScaled.updateScale(tokenInterestRate());
514
567
  if (_scr.scr == 0) {
@@ -529,7 +582,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
529
582
  uint256 scrAmount,
530
583
  uint256 policyInterestRate,
531
584
  int256 adjustment
532
- ) external override onlyBorrower {
585
+ ) external override onlyBorrower whenNotPaused {
533
586
  require(scrAmount <= uint256(_scr.scr), "Current SCR less than the amount you want to unlock");
534
587
  _tsScaled.updateScale(tokenInterestRate());
535
588
 
@@ -586,6 +639,12 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
586
639
  onlyPolicyPool
587
640
  returns (uint256)
588
641
  {
642
+ /**
643
+ * Here we don't check for maxUtilizationRate because that limit only affects locking more capital (`lockScr`), but
644
+ * doesn't affects the right of liquidity providers to withdraw their funds.
645
+ * The only limit for withdraws is the `totalWithdrawable()` function, that's affected by the relation between the
646
+ * scr and the totalSupply.
647
+ */
589
648
  amount = Math.min(amount, Math.min(balanceOf(provider), totalWithdrawable()));
590
649
  if (amount == 0) return 0;
591
650
  _burn(provider, amount);
@@ -595,6 +654,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
595
654
  }
596
655
 
597
656
  function addBorrower(address borrower) external override onlyPolicyPool {
657
+ require(borrower != address(0), "EToken: Borrower cannot be the zero address");
598
658
  TimeScaled.ScaledAmount storage loan = _loans[borrower];
599
659
  if (loan.scale == 0) {
600
660
  loan.init();
@@ -603,6 +663,7 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
603
663
  }
604
664
 
605
665
  function removeBorrower(address borrower) external override onlyPolicyPool {
666
+ require(borrower != address(0), "EToken: Borrower cannot be the zero address");
606
667
  uint256 defaultedDebt = getLoan(borrower);
607
668
  delete _loans[borrower];
608
669
  emit InternalBorrowerRemoved(borrower, defaultedDebt);
@@ -627,14 +688,16 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
627
688
  return amountAsked - amount;
628
689
  }
629
690
 
630
- function repayLoan(uint256 amount, address onBehalfOf) external override {
691
+ function repayLoan(uint256 amount, address onBehalfOf) external override whenNotPaused {
692
+ require(amount > 0, "EToken: amount should be greater than zero.");
631
693
  // Anyone can call this method, since it has to pay
632
- currency().safeTransferFrom(_msgSender(), address(this), amount);
633
694
  TimeScaled.ScaledAmount storage loan = _loans[onBehalfOf];
634
695
  require(loan.scale != 0, "Not a registered borrower");
635
696
  loan.sub(amount, internalLoanInterestRate());
636
697
  _discreteChange(int256(amount));
637
698
  emit InternalLoanRepaid(onBehalfOf, amount);
699
+ // Interaction at the end for security reasons
700
+ currency().safeTransferFrom(_msgSender(), address(this), amount);
638
701
  }
639
702
 
640
703
  function getLoan(address borrower) public view virtual override returns (uint256) {
@@ -705,4 +768,11 @@ contract EToken is Reserve, IERC20Metadata, IEToken {
705
768
  function whitelist() external view returns (ILPWhitelist) {
706
769
  return _params.whitelist;
707
770
  }
771
+
772
+ /**
773
+ * @dev This empty reserved space is put in place to allow future versions to add new
774
+ * variables without shifting down storage in the inheritance chain.
775
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
776
+ */
777
+ uint256[41] private __gap;
708
778
  }
@@ -34,8 +34,17 @@ contract LPManualWhitelist is ILPWhitelist, PolicyPoolComponent {
34
34
  external
35
35
  onlyComponentRole(LP_WHITELIST_ROLE)
36
36
  {
37
- _whitelisted[provider] = whitelisted;
38
- emit LPWhitelisted(provider, whitelisted);
37
+ if (_whitelisted[provider] != whitelisted) {
38
+ _whitelisted[provider] = whitelisted;
39
+ emit LPWhitelisted(provider, whitelisted);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @dev See {IERC165-supportsInterface}.
45
+ */
46
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
47
+ return super.supportsInterface(interfaceId) || interfaceId == type(ILPWhitelist).interfaceId;
39
48
  }
40
49
 
41
50
  function acceptsDeposit(
@@ -54,4 +63,11 @@ contract LPManualWhitelist is ILPWhitelist, PolicyPoolComponent {
54
63
  ) external view override returns (bool) {
55
64
  return _whitelisted[providerTo];
56
65
  }
66
+
67
+ /**
68
+ * @dev This empty reserved space is put in place to allow future versions to add new
69
+ * variables without shifting down storage in the inheritance chain.
70
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
71
+ */
72
+ uint256[49] private __gap;
57
73
  }
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
5
5
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
6
7
  import {IPolicyPoolComponent} from "./interfaces/IPolicyPoolComponent.sol";
7
8
  import {IAssetManager} from "./interfaces/IAssetManager.sol";
8
9
  import {IAccessManager} from "./interfaces/IAccessManager.sol";
@@ -17,6 +18,8 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
17
18
  * cash at liquidityMiddle.
18
19
  * @custom:security-contact security@ensuro.co
19
20
  * @author Ensuro
21
+ *
22
+ * @notice This contracts uses Diamond Storage and should not define state variables outside of that. See the diamondStorage method for more details.
20
23
  */
21
24
  abstract contract LiquidityThresholdAssetManager is IAssetManager {
22
25
  using SafeCast for uint256;
@@ -37,6 +40,10 @@ abstract contract LiquidityThresholdAssetManager is IAssetManager {
37
40
  }
38
41
 
39
42
  constructor(IERC20Metadata asset_) {
43
+ require(
44
+ address(asset_) != address(0),
45
+ "LiquidityThresholdAssetManager: asset cannot be zero address"
46
+ );
40
47
  _asset = asset_;
41
48
  }
42
49
 
@@ -96,8 +103,8 @@ abstract contract LiquidityThresholdAssetManager is IAssetManager {
96
103
  }
97
104
 
98
105
  /**
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
106
+ * @dev This is called from PolicyPool when doesn't have enough money for payment.
107
+ * After the call, there should be enough money in PolicyPool.currency().balanceOf(this) to
101
108
  * do the payment
102
109
  * @param paymentAmount The amount of the payment
103
110
  */
@@ -157,4 +164,12 @@ abstract contract LiquidityThresholdAssetManager is IAssetManager {
157
164
  emit GovernanceAction(IAccessManager.GovernanceActions.setLiquidityMax, max);
158
165
  }
159
166
  }
167
+
168
+ /**
169
+ * @dev See {IERC165-supportsInterface}.
170
+ */
171
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
172
+ return
173
+ interfaceId == type(IERC165).interfaceId || interfaceId == type(IAssetManager).interfaceId;
174
+ }
160
175
  }
@@ -102,7 +102,9 @@ library Policy {
102
102
  return (policy.srCoc * (block.timestamp - policy.start)) / (policy.expiration - policy.start);
103
103
  }
104
104
 
105
- function hash(PolicyData memory policy) internal pure returns (bytes32) {
106
- return keccak256(abi.encode(policy));
105
+ function hash(PolicyData memory policy) internal pure returns (bytes32 retHash) {
106
+ retHash = keccak256(abi.encode(policy));
107
+ require(retHash != bytes32(0), "Policy: hash cannot be 0");
108
+ return retHash;
107
109
  }
108
110
  }
@@ -166,7 +166,7 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
166
166
  }
167
167
 
168
168
  /**
169
- * @dev Modifier that checks the caller has any of the given rolea
169
+ * @dev Modifier that checks the caller has any of the given roles
170
170
  */
171
171
  modifier onlyRole2(bytes32 role1, bytes32 role2) {
172
172
  _access.checkRole2(role1, role2, _msgSender());
@@ -182,6 +182,9 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
182
182
  */
183
183
  /// @custom:oz-upgrades-unsafe-allow constructor
184
184
  constructor(IAccessManager access_, IERC20Metadata currency_) {
185
+ require(address(access_) != address(0), "PolicyPool: access cannot be zero address");
186
+ require(address(currency_) != address(0), "PolicyPool: currency cannot be zero address");
187
+ _disableInitializers();
185
188
  _access = access_;
186
189
  _currency = currency_;
187
190
  }
@@ -198,15 +201,24 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
198
201
  string memory symbol_,
199
202
  address treasury_
200
203
  ) public initializer {
204
+ require(bytes(name_).length > 0, "PolicyPool: name cannot be empty");
205
+ require(bytes(symbol_).length > 0, "PolicyPool: symbol cannot be empty");
201
206
  __UUPSUpgradeable_init();
202
207
  __ERC721_init(name_, symbol_);
203
208
  __Pausable_init();
204
209
  __PolicyPool_init_unchained(treasury_);
205
210
  }
206
211
 
212
+ /**
213
+ * @dev See {IERC165-supportsInterface}.
214
+ */
215
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
216
+ return super.supportsInterface(interfaceId) || interfaceId == type(IPolicyPool).interfaceId;
217
+ }
218
+
207
219
  // solhint-disable-next-line func-name-mixedcase
208
- function __PolicyPool_init_unchained(address treasury_) internal initializer {
209
- _treasury = treasury_;
220
+ function __PolicyPool_init_unchained(address treasury_) internal onlyInitializing {
221
+ _setTreasury(treasury_);
210
222
  }
211
223
 
212
224
  // solhint-disable-next-line no-empty-blocks
@@ -241,6 +253,12 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
241
253
  return _currency;
242
254
  }
243
255
 
256
+ function _setTreasury(address treasury_) internal {
257
+ require(treasury_ != address(0), "PolicyPool: treasury cannot be the zero address");
258
+ _treasury = treasury_;
259
+ emit ComponentChanged(IAccessManager.GovernanceActions.setTreasury, _treasury);
260
+ }
261
+
244
262
  /**
245
263
  * @dev Changes the address of the treasury, the one that receives the protocol fees.
246
264
  *
@@ -251,8 +269,7 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
251
269
  * - Emits {ComponentChanged} with action = setTreasury and the address of the new treasury.
252
270
  */
253
271
  function setTreasury(address treasury_) external onlyRole(LEVEL1_ROLE) {
254
- _treasury = treasury_;
255
- emit ComponentChanged(IAccessManager.GovernanceActions.setTreasury, _treasury);
272
+ _setTreasury(treasury_);
256
273
  }
257
274
 
258
275
  /**
@@ -285,6 +302,15 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
285
302
  require(comp.status == ComponentStatus.inactive, "Component already in the pool");
286
303
  require(component.policyPool() == this, "Component not linked to this pool");
287
304
 
305
+ require(
306
+ (kind == ComponentKind.eToken && component.supportsInterface(type(IEToken).interfaceId)) ||
307
+ (kind == ComponentKind.premiumsAccount &&
308
+ component.supportsInterface(type(IPremiumsAccount).interfaceId)) ||
309
+ (kind == ComponentKind.riskModule &&
310
+ component.supportsInterface(type(IRiskModule).interfaceId)),
311
+ "PolicyPool: Not the right kind"
312
+ );
313
+
288
314
  comp.status = ComponentStatus.active;
289
315
  comp.kind = kind;
290
316
  if (kind == ComponentKind.premiumsAccount) {
@@ -406,8 +432,9 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
406
432
 
407
433
  function deposit(IEToken eToken, uint256 amount) external override whenNotPaused {
408
434
  require(_etkStatus(eToken) == ComponentStatus.active, "eToken is not active");
435
+ uint256 balanceBefore = _currency.balanceOf(address(eToken));
409
436
  _currency.safeTransferFrom(_msgSender(), address(eToken), amount);
410
- eToken.deposit(_msgSender(), amount);
437
+ eToken.deposit(_msgSender(), _currency.balanceOf(address(eToken)) - balanceBefore);
411
438
  }
412
439
 
413
440
  function withdraw(IEToken eToken, uint256 amount)
@@ -431,15 +458,20 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
431
458
  address policyHolder,
432
459
  uint96 internalId
433
460
  ) external override whenNotPaused returns (uint256) {
461
+ // Checks
434
462
  IRiskModule rm = policy.riskModule;
435
463
  require(address(rm) == _msgSender(), "Only the RM can create new policies");
436
464
  require(_rmStatus(rm) == ComponentStatus.active, "RM module not found or not active");
437
- policy.id = (uint256(uint160(address(rm))) << 96) + internalId;
438
- _policies[policy.id] = policy.hash();
439
465
  IPremiumsAccount pa = rm.premiumsAccount();
440
466
  require(_paStatus(pa) == ComponentStatus.active, "PremiumsAccount not found or not active");
467
+
468
+ // Effects
469
+ policy.id = (uint256(uint160(address(rm))) << 96) + internalId;
470
+ require(_policies[policy.id] == bytes32(0), "Policy already exists");
471
+ _policies[policy.id] = policy.hash();
472
+
473
+ // Interactions
441
474
  pa.policyCreated(policy);
442
- _safeMint(policyHolder, policy.id, "");
443
475
 
444
476
  // Distribute the premium
445
477
  _currency.safeTransferFrom(payer, address(pa), policy.purePremium);
@@ -448,7 +480,12 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
448
480
  _currency.safeTransferFrom(payer, _treasury, policy.ensuroCommission);
449
481
  if (policy.partnerCommission > 0 && payer != rm.wallet())
450
482
  _currency.safeTransferFrom(payer, rm.wallet(), policy.partnerCommission);
451
- // TODO: this code does up to 5 ERC20 transfers. How we can avoid this? Delayed transfers?
483
+ /**
484
+ * This code does up to 5 ERC20 transfers. This can be avoided to reduce the gas cost, by implementing delayed
485
+ * transfers. This might be considered in the future, but to avoid increasing the complexity and since so far we
486
+ * operate on low gas-cost blockchains, we keep it as it is.
487
+ */
488
+ _safeMint(policyHolder, policy.id, "");
452
489
 
453
490
  emit NewPolicy(rm, policy);
454
491
  return policy.id;
@@ -499,6 +536,7 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
499
536
  uint256 payout,
500
537
  bool expired
501
538
  ) internal {
539
+ // Checks
502
540
  _validatePolicy(policy);
503
541
  IRiskModule rm = policy.riskModule;
504
542
  require(expired || address(rm) == _msgSender(), "Only the RM can resolve policies");
@@ -518,6 +556,9 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
518
556
  compStatus == ComponentStatus.active || compStatus == ComponentStatus.deprecated,
519
557
  "PremiumsAccount must be active or deprecated to process resolutions"
520
558
  );
559
+ // Effects
560
+ delete _policies[policy.id];
561
+ // Interactions
521
562
  if (customerWon) {
522
563
  address policyOwner = ownerOf(policy.id);
523
564
  pa.policyResolvedWithPayout(policyOwner, policy, payout);
@@ -528,7 +569,6 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
528
569
  rm.releaseExposure(policy.payout);
529
570
 
530
571
  emit PolicyResolved(policy.riskModule, policy.id, payout);
531
- delete _policies[policy.id];
532
572
  if (payout > 0) {
533
573
  _notifyPayout(policy.id, payout);
534
574
  } else {
@@ -584,4 +624,11 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
584
624
  ) internal override whenNotPaused {
585
625
  super._beforeTokenTransfer(from, to, tokenId);
586
626
  }
627
+
628
+ /**
629
+ * @dev This empty reserved space is put in place to allow future versions to add new
630
+ * variables without shifting down storage in the inheritance chain.
631
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
632
+ */
633
+ uint256[47] private __gap;
587
634
  }
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
5
5
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
6
7
  import {IPolicyPool} from "./interfaces/IPolicyPool.sol";
7
8
  import {IPolicyPoolComponent} from "./interfaces/IPolicyPoolComponent.sol";
8
9
  import {IAccessManager} from "./interfaces/IAccessManager.sol";
@@ -14,8 +15,8 @@ import {WadRayMath} from "./dependencies/WadRayMath.sol";
14
15
  * @dev This is the base class of all the components of the protocol that are linked to the PolicyPool and created
15
16
  * after it.
16
17
  * Holds the reference to _policyPool as immutable, also provides access to common admin roles:
17
- * - LEVEL1_ROLE: High impact changes like upgrades or other critical operations
18
- * - LEVEL2_ROLE: Mid-impact changes like adding new risk modules or changing some parameters
18
+ * - LEVEL1_ROLE: High impact changes like upgrades, adding or removing components or other critical operations
19
+ * - LEVEL2_ROLE: Mid-impact changes like changing some parameters
19
20
  * - LEVEL3_ROLE: Low-impact changes like changing some parameters up to given percentage (tweaks)
20
21
  * - GUARDIAN_ROLE: For emergency operations oriented to protect the protocol in case of attacks or hacking.
21
22
  *
@@ -79,11 +80,16 @@ abstract contract PolicyPoolComponent is
79
80
 
80
81
  /// @custom:oz-upgrades-unsafe-allow constructor
81
82
  constructor(IPolicyPool policyPool_) {
83
+ require(
84
+ address(policyPool_) != address(0),
85
+ "PolicyPoolComponent: policyPool cannot be zero address"
86
+ );
87
+ _disableInitializers();
82
88
  _policyPool = policyPool_;
83
89
  }
84
90
 
85
91
  // solhint-disable-next-line func-name-mixedcase
86
- function __PolicyPoolComponent_init() internal initializer {
92
+ function __PolicyPoolComponent_init() internal onlyInitializing {
87
93
  __UUPSUpgradeable_init();
88
94
  __Pausable_init();
89
95
  }
@@ -100,6 +106,15 @@ abstract contract PolicyPoolComponent is
100
106
  );
101
107
  }
102
108
 
109
+ /**
110
+ * @dev See {IERC165-supportsInterface}.
111
+ */
112
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
113
+ return
114
+ interfaceId == type(IERC165).interfaceId ||
115
+ interfaceId == type(IPolicyPoolComponent).interfaceId;
116
+ }
117
+
103
118
  function pause() public onlyGlobalOrComponentRole(GUARDIAN_ROLE) {
104
119
  _pause();
105
120
  }
@@ -171,4 +186,11 @@ abstract contract PolicyPoolComponent is
171
186
  }
172
187
  }
173
188
  }
189
+
190
+ /**
191
+ * @dev This empty reserved space is put in place to allow future versions to add new
192
+ * variables without shifting down storage in the inheritance chain.
193
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
194
+ */
195
+ uint256[49] private __gap;
174
196
  }