@ensuro/core 2.0.0-beta9 → 2.0.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 (56) hide show
  1. package/README.md +14 -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 +21 -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 +1110 -0
  14. package/build/contracts/TimeScaled.sol/TimeScaled.json +2 -2
  15. package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +137 -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/IPolicyPool.sol/IPolicyPool.json +19 -0
  19. package/build/contracts/interfaces/IPolicyPoolComponent.sol/IPolicyPoolComponent.json +19 -0
  20. package/build/contracts/mocks/FixedRateVault.sol/FixedRateVault.json +2 -2
  21. package/build/contracts/mocks/ForwardProxy.sol/ForwardProxy.json +2 -2
  22. package/build/contracts/mocks/InterfaceIdCalculator.sol/InterfaceIdCalculator.json +180 -0
  23. package/build/contracts/mocks/PolicyHolderMock.sol/PolicyHolderMock.json +2 -2
  24. package/build/contracts/mocks/PolicyPoolComponentMock.sol/PolicyPoolComponentMock.json +21 -2
  25. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMock.json +21 -2
  26. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMockForward.json +2 -2
  27. package/build/contracts/mocks/RiskModuleMock.sol/RiskModuleMock.json +21 -2
  28. package/build/contracts/mocks/TestCurrency.sol/TestCurrency.json +2 -2
  29. package/build/contracts/mocks/TestNFT.sol/TestNFT.json +2 -2
  30. package/contracts/AccessManager.sol +30 -1
  31. package/contracts/ERC4626AssetManager.sol +3 -0
  32. package/contracts/EToken.sol +89 -19
  33. package/contracts/LPManualWhitelist.sol +18 -2
  34. package/contracts/LiquidityThresholdAssetManager.sol +17 -2
  35. package/contracts/Policy.sol +4 -2
  36. package/contracts/PolicyPool.sol +72 -13
  37. package/contracts/PolicyPoolComponent.sol +29 -3
  38. package/contracts/PremiumsAccount.sol +72 -24
  39. package/contracts/Reserve.sol +19 -3
  40. package/contracts/RiskModule.sol +46 -13
  41. package/contracts/SignedQuoteRiskModule.sol +310 -0
  42. package/contracts/TrustfulRiskModule.sol +104 -3
  43. package/contracts/interfaces/IAssetManager.sol +8 -3
  44. package/contracts/interfaces/IPolicyPool.sol +15 -4
  45. package/contracts/interfaces/IPolicyPoolComponent.sol +2 -1
  46. package/contracts/mocks/InterfaceIdCalculator.sol +32 -0
  47. package/contracts/mocks/PolicyPoolComponentMock.sol +10 -0
  48. package/contracts/mocks/PolicyPoolMock.sol +4 -0
  49. package/js/deploy.js +734 -0
  50. package/js/test-utils.js +139 -37
  51. package/package.json +4 -2
  52. package/scripts/change-pragma.sh +19 -0
  53. package/scripts/deploySmokeTest-fork.sh +53 -0
  54. package/scripts/deploySmokeTest.sh +51 -0
  55. package/scripts/storageLayout.js +33 -0
  56. package/scripts/utils.sh +65 -0
@@ -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,19 +201,36 @@ 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
- // solhint-disable-next-line no-empty-blocks
213
- function _authorizeUpgrade(address) internal override onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {}
224
+ function _authorizeUpgrade(address newImpl)
225
+ internal
226
+ view
227
+ override
228
+ onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE)
229
+ {
230
+ IPolicyPool newPool = IPolicyPool(newImpl);
231
+ require(newPool.access() == _access, "Can't upgrade changing the access manager");
232
+ require(newPool.currency() == _currency, "Can't upgrade changing the currency");
233
+ }
214
234
 
215
235
  /**
216
236
  * @dev Pauses the contract. When the contract is paused, several operations are rejected: deposits, withdrawals, new
@@ -241,6 +261,12 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
241
261
  return _currency;
242
262
  }
243
263
 
264
+ function _setTreasury(address treasury_) internal {
265
+ require(treasury_ != address(0), "PolicyPool: treasury cannot be the zero address");
266
+ _treasury = treasury_;
267
+ emit ComponentChanged(IAccessManager.GovernanceActions.setTreasury, _treasury);
268
+ }
269
+
244
270
  /**
245
271
  * @dev Changes the address of the treasury, the one that receives the protocol fees.
246
272
  *
@@ -251,8 +277,7 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
251
277
  * - Emits {ComponentChanged} with action = setTreasury and the address of the new treasury.
252
278
  */
253
279
  function setTreasury(address treasury_) external onlyRole(LEVEL1_ROLE) {
254
- _treasury = treasury_;
255
- emit ComponentChanged(IAccessManager.GovernanceActions.setTreasury, _treasury);
280
+ _setTreasury(treasury_);
256
281
  }
257
282
 
258
283
  /**
@@ -285,6 +310,15 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
285
310
  require(comp.status == ComponentStatus.inactive, "Component already in the pool");
286
311
  require(component.policyPool() == this, "Component not linked to this pool");
287
312
 
313
+ require(
314
+ (kind == ComponentKind.eToken && component.supportsInterface(type(IEToken).interfaceId)) ||
315
+ (kind == ComponentKind.premiumsAccount &&
316
+ component.supportsInterface(type(IPremiumsAccount).interfaceId)) ||
317
+ (kind == ComponentKind.riskModule &&
318
+ component.supportsInterface(type(IRiskModule).interfaceId)),
319
+ "PolicyPool: Not the right kind"
320
+ );
321
+
288
322
  comp.status = ComponentStatus.active;
289
323
  comp.kind = kind;
290
324
  if (kind == ComponentKind.premiumsAccount) {
@@ -406,8 +440,9 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
406
440
 
407
441
  function deposit(IEToken eToken, uint256 amount) external override whenNotPaused {
408
442
  require(_etkStatus(eToken) == ComponentStatus.active, "eToken is not active");
443
+ uint256 balanceBefore = _currency.balanceOf(address(eToken));
409
444
  _currency.safeTransferFrom(_msgSender(), address(eToken), amount);
410
- eToken.deposit(_msgSender(), amount);
445
+ eToken.deposit(_msgSender(), _currency.balanceOf(address(eToken)) - balanceBefore);
411
446
  }
412
447
 
413
448
  function withdraw(IEToken eToken, uint256 amount)
@@ -431,15 +466,20 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
431
466
  address policyHolder,
432
467
  uint96 internalId
433
468
  ) external override whenNotPaused returns (uint256) {
469
+ // Checks
434
470
  IRiskModule rm = policy.riskModule;
435
471
  require(address(rm) == _msgSender(), "Only the RM can create new policies");
436
472
  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
473
  IPremiumsAccount pa = rm.premiumsAccount();
440
474
  require(_paStatus(pa) == ComponentStatus.active, "PremiumsAccount not found or not active");
475
+
476
+ // Effects
477
+ policy.id = (uint256(uint160(address(rm))) << 96) + internalId;
478
+ require(_policies[policy.id] == bytes32(0), "Policy already exists");
479
+ _policies[policy.id] = policy.hash();
480
+
481
+ // Interactions
441
482
  pa.policyCreated(policy);
442
- _safeMint(policyHolder, policy.id, "");
443
483
 
444
484
  // Distribute the premium
445
485
  _currency.safeTransferFrom(payer, address(pa), policy.purePremium);
@@ -448,7 +488,12 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
448
488
  _currency.safeTransferFrom(payer, _treasury, policy.ensuroCommission);
449
489
  if (policy.partnerCommission > 0 && payer != rm.wallet())
450
490
  _currency.safeTransferFrom(payer, rm.wallet(), policy.partnerCommission);
451
- // TODO: this code does up to 5 ERC20 transfers. How we can avoid this? Delayed transfers?
491
+ /**
492
+ * This code does up to 5 ERC20 transfers. This can be avoided to reduce the gas cost, by implementing delayed
493
+ * transfers. This might be considered in the future, but to avoid increasing the complexity and since so far we
494
+ * operate on low gas-cost blockchains, we keep it as it is.
495
+ */
496
+ _safeMint(policyHolder, policy.id, "");
452
497
 
453
498
  emit NewPolicy(rm, policy);
454
499
  return policy.id;
@@ -483,6 +528,10 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
483
528
  return _policies[policyId] != bytes32(0);
484
529
  }
485
530
 
531
+ function getPolicyHash(uint256 policyId) external view override returns (bytes32) {
532
+ return _policies[policyId];
533
+ }
534
+
486
535
  /**
487
536
  * @dev Internal function that handles the different alternative resolutions for a policy, with or without payout and
488
537
  * expiration.
@@ -499,6 +548,7 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
499
548
  uint256 payout,
500
549
  bool expired
501
550
  ) internal {
551
+ // Checks
502
552
  _validatePolicy(policy);
503
553
  IRiskModule rm = policy.riskModule;
504
554
  require(expired || address(rm) == _msgSender(), "Only the RM can resolve policies");
@@ -518,6 +568,9 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
518
568
  compStatus == ComponentStatus.active || compStatus == ComponentStatus.deprecated,
519
569
  "PremiumsAccount must be active or deprecated to process resolutions"
520
570
  );
571
+ // Effects
572
+ delete _policies[policy.id];
573
+ // Interactions
521
574
  if (customerWon) {
522
575
  address policyOwner = ownerOf(policy.id);
523
576
  pa.policyResolvedWithPayout(policyOwner, policy, payout);
@@ -528,7 +581,6 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
528
581
  rm.releaseExposure(policy.payout);
529
582
 
530
583
  emit PolicyResolved(policy.riskModule, policy.id, payout);
531
- delete _policies[policy.id];
532
584
  if (payout > 0) {
533
585
  _notifyPayout(policy.id, payout);
534
586
  } else {
@@ -584,4 +636,11 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
584
636
  ) internal override whenNotPaused {
585
637
  super._beforeTokenTransfer(from, to, tokenId);
586
638
  }
639
+
640
+ /**
641
+ * @dev This empty reserved space is put in place to allow future versions to add new
642
+ * variables without shifting down storage in the inheritance chain.
643
+ * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
644
+ */
645
+ uint256[47] private __gap;
587
646
  }