@ensuro/core 2.8.0-beta1 → 2.8.0-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,7 @@ This contract also follows the ERC721 standard, minting an NFT for each policy c
68
68
 
69
69
  The protocol uses three levels of access control, plus a guardian role. These roles can be assigned at protocol level or specifically for a component. The roles are managed by the AccessManager smart contract.
70
70
 
71
- More info about governance in https://docs.google.com/spreadsheets/d/1LqlogRn8AlnLq1rPTd5UT7CJI3uc31PdBaxj4pX3mtE/edit?usp=sharing
71
+ More info about governance in [our docs](https://docs.ensuro.co/ensuro-docs/smart-contracts/governance)
72
72
 
73
73
  ## Upgradability
74
74
 
@@ -81,7 +81,7 @@ We will never deploy upgrades to live contracts without prior notice to the user
81
81
  - LEVEL1_ROLE: this will be delegated to a Timelock contract that will give enough time to the users to be notified of the imminent upgrade.
82
82
  - GUARDIAN_ROLE: this will used only for emergency situations to prevent hacks or fix vulnerabilities. It will be delegated to multisigs where one of the signers is a trusted third party.
83
83
 
84
- Have in mind the new versions of the contracts might or might not be covered by the same audit processes as the initial ones. Always check the details of the audit reports.
84
+ Have in mind the new versions of the contracts might or might not be covered by the same audit processes as the initial ones. See our [audit applicability matrix](https://docs.ensuro.co/ensuro-docs/deployments/audits) to check which audit applies to the currently deployed contracts.
85
85
 
86
86
  ## Development
87
87
 
@@ -40,8 +40,9 @@ library Policy {
40
40
  uint256 premium,
41
41
  uint256 payout,
42
42
  uint256 lossProb,
43
- uint40 expiration
44
- ) internal view returns (PolicyData memory newPolicy) {
43
+ uint40 expiration,
44
+ uint40 start
45
+ ) internal pure returns (PolicyData memory newPolicy) {
45
46
  require(premium <= payout, "Premium cannot be more than payout");
46
47
  PolicyData memory policy;
47
48
 
@@ -49,7 +50,7 @@ library Policy {
49
50
  policy.premium = premium;
50
51
  policy.payout = payout;
51
52
  policy.lossProb = lossProb;
52
- policy.start = uint40(block.timestamp);
53
+ policy.start = start;
53
54
  policy.expiration = expiration;
54
55
  policy.purePremium = payout.wadMul(lossProb.wadMul(rmParams.moc));
55
56
  // Calculate Junior and Senior SCR
@@ -125,25 +125,6 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
125
125
  */
126
126
  string internal _nftBaseURI;
127
127
 
128
- /**
129
- * @dev Event emitted every time a new policy is added to the pool. Contains all the data about the policy that is
130
- * later required for doing operations with the policy like resolution or expiration.
131
- *
132
- * @param riskModule The risk module that created the policy
133
- * @param policy The {Policy-PolicyData} struct with all the immutable fields of the policy.
134
- */
135
- event NewPolicy(IRiskModule indexed riskModule, Policy.PolicyData policy);
136
-
137
- /**
138
- * @dev Event emitted every time a policy is removed from the pool. If the policy expired, the `payout` is 0,
139
- * otherwise is the amount transferred to the policyholder.
140
- *
141
- * @param riskModule The risk module where that created the policy initially.
142
- * @param policyId The unique id of the policy
143
- * @param payout The payout that has been paid to the policy holder. 0 when the policy expired.
144
- */
145
- event PolicyResolved(IRiskModule indexed riskModule, uint256 indexed policyId, uint256 payout);
146
-
147
128
  /**
148
129
  * @dev Event emitted when the treasury changes
149
130
  *
@@ -400,39 +381,34 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
400
381
  return _components[component].status;
401
382
  }
402
383
 
403
- function _etkStatus(IEToken eToken) internal view returns (ComponentStatus) {
404
- Component storage comp = _components[IPolicyPoolComponent(address(eToken))];
405
- require(comp.kind == ComponentKind.eToken, "Component is not an eToken");
384
+ function _componentStatus(address component, ComponentKind kind) internal view returns (ComponentStatus) {
385
+ Component storage comp = _components[IPolicyPoolComponent(component)];
386
+ require(comp.kind == kind, "Component is not of the right kind");
406
387
  return comp.status;
407
388
  }
408
389
 
409
- function _rmStatus(IRiskModule riskModule) internal view returns (ComponentStatus) {
410
- Component storage comp = _components[IPolicyPoolComponent(address(riskModule))];
411
- require(comp.kind == ComponentKind.riskModule, "Component is not a RiskModule");
412
- return comp.status;
390
+ function _requireCompActive(address component, ComponentKind kind) internal view {
391
+ require(_componentStatus(component, kind) == ComponentStatus.active, "Component not found or not active");
413
392
  }
414
393
 
415
- function _paStatus(IPremiumsAccount premiumsAccount) internal view returns (ComponentStatus) {
416
- Component storage comp = _components[IPolicyPoolComponent(address(premiumsAccount))];
417
- require(comp.kind == ComponentKind.premiumsAccount, "Component is not a PremiumsAccount");
418
- return comp.status;
394
+ function _requireCompActiveOrDeprecated(address component, ComponentKind kind) internal view {
395
+ ComponentStatus status = _componentStatus(component, kind);
396
+ require(
397
+ status == ComponentStatus.active || status == ComponentStatus.deprecated,
398
+ "Component must be active or deprecated"
399
+ );
419
400
  }
420
401
 
421
402
  function deposit(IEToken eToken, uint256 amount) external override whenNotPaused {
422
- require(_etkStatus(eToken) == ComponentStatus.active, "eToken is not active");
403
+ _requireCompActive(address(eToken), ComponentKind.eToken);
423
404
  uint256 balanceBefore = _currency.balanceOf(address(eToken));
424
405
  _currency.safeTransferFrom(_msgSender(), address(eToken), amount);
425
406
  eToken.deposit(_msgSender(), _currency.balanceOf(address(eToken)) - balanceBefore);
426
407
  }
427
408
 
428
409
  function withdraw(IEToken eToken, uint256 amount) external override whenNotPaused returns (uint256) {
429
- ComponentStatus etkStatus = _etkStatus(eToken);
430
- require(
431
- etkStatus == ComponentStatus.active || etkStatus == ComponentStatus.deprecated,
432
- "eToken not found or withdraws not allowed"
433
- );
434
- address provider = _msgSender();
435
- return eToken.withdraw(provider, amount);
410
+ _requireCompActiveOrDeprecated(address(eToken), ComponentKind.eToken);
411
+ return eToken.withdraw(_msgSender(), amount);
436
412
  }
437
413
 
438
414
  function newPolicy(
@@ -444,14 +420,15 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
444
420
  // Checks
445
421
  IRiskModule rm = policy.riskModule;
446
422
  require(address(rm) == _msgSender(), "Only the RM can create new policies");
447
- require(_rmStatus(rm) == ComponentStatus.active, "RM module not found or not active");
423
+ _requireCompActive(address(rm), ComponentKind.riskModule);
448
424
  IPremiumsAccount pa = rm.premiumsAccount();
449
- require(_paStatus(pa) == ComponentStatus.active, "PremiumsAccount not found or not active");
425
+ _requireCompActive(address(pa), ComponentKind.premiumsAccount);
450
426
 
451
427
  // Effects
452
- policy.id = (uint256(uint160(address(rm))) << 96) + internalId;
428
+ policy.id = makePolicyId(rm, internalId);
453
429
  require(_policies[policy.id] == bytes32(0), "Policy already exists");
454
430
  _policies[policy.id] = policy.hash();
431
+ _safeMint(policyHolder, policy.id, "");
455
432
 
456
433
  // Interactions
457
434
  pa.policyCreated(policy);
@@ -468,16 +445,81 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
468
445
  * transfers. This might be considered in the future, but to avoid increasing the complexity and since so far we
469
446
  * operate on low gas-cost blockchains, we keep it as it is.
470
447
  */
471
- _safeMint(policyHolder, policy.id, "");
472
448
 
473
449
  emit NewPolicy(rm, policy);
474
450
  return policy.id;
475
451
  }
476
452
 
453
+ function replacePolicy(
454
+ Policy.PolicyData memory oldPolicy,
455
+ Policy.PolicyData memory newPolicy_,
456
+ address payer,
457
+ uint96 internalId
458
+ ) external override whenNotPaused returns (uint256) {
459
+ // Checks
460
+ _validatePolicy(oldPolicy);
461
+ IRiskModule rm = oldPolicy.riskModule;
462
+ require(address(rm) == _msgSender(), "Only the RM can create new policies");
463
+ _requireCompActive(address(rm), ComponentKind.riskModule);
464
+ IPremiumsAccount pa = rm.premiumsAccount();
465
+ _requireCompActive(address(pa), ComponentKind.premiumsAccount);
466
+ require(oldPolicy.expiration > uint40(block.timestamp), "Old policy is expired");
467
+ require(oldPolicy.start == newPolicy_.start, "Both policies must have the same starting date");
468
+ require(
469
+ oldPolicy.payout <= newPolicy_.payout &&
470
+ oldPolicy.purePremium <= newPolicy_.purePremium &&
471
+ oldPolicy.ensuroCommission <= newPolicy_.ensuroCommission &&
472
+ oldPolicy.jrCoc <= newPolicy_.jrCoc &&
473
+ oldPolicy.srCoc <= newPolicy_.srCoc &&
474
+ oldPolicy.jrScr <= newPolicy_.jrScr &&
475
+ oldPolicy.srScr <= newPolicy_.srScr &&
476
+ oldPolicy.partnerCommission <= newPolicy_.partnerCommission &&
477
+ oldPolicy.expiration <= newPolicy_.expiration &&
478
+ rm == newPolicy_.riskModule,
479
+ "New policy must be greater or equal than old policy"
480
+ );
481
+
482
+ // Effects
483
+ newPolicy_.id = makePolicyId(rm, internalId);
484
+ require(_policies[newPolicy_.id] == bytes32(0), "Policy already exists");
485
+ _policies[newPolicy_.id] = newPolicy_.hash();
486
+ address policyHolder = ownerOf(oldPolicy.id);
487
+ _safeMint(policyHolder, newPolicy_.id, "");
488
+ delete _policies[oldPolicy.id];
489
+
490
+ // Interactions
491
+ pa.policyReplaced(oldPolicy, newPolicy_);
492
+
493
+ // Distribute the premium
494
+ uint256 aux = newPolicy_.purePremium - oldPolicy.purePremium;
495
+ if (aux > 0) _currency.safeTransferFrom(payer, address(pa), aux);
496
+ aux = newPolicy_.srCoc - oldPolicy.srCoc;
497
+ if (aux > 0) _currency.safeTransferFrom(payer, address(pa.seniorEtk()), aux);
498
+ aux = newPolicy_.jrCoc - oldPolicy.jrCoc;
499
+ if (aux > 0) _currency.safeTransferFrom(payer, address(pa.juniorEtk()), aux);
500
+ aux = newPolicy_.ensuroCommission - oldPolicy.ensuroCommission;
501
+ if (aux > 0) _currency.safeTransferFrom(payer, _treasury, aux);
502
+ aux = newPolicy_.partnerCommission - oldPolicy.partnerCommission;
503
+ if (aux > 0 && payer != rm.wallet()) _currency.safeTransferFrom(payer, rm.wallet(), aux);
504
+ /**
505
+ * This code does up to 5 ERC20 transfers. This can be avoided to reduce the gas cost, by implementing delayed
506
+ * transfers. This might be considered in the future, but to avoid increasing the complexity and since so far we
507
+ * operate on low gas-cost blockchains, we keep it as it is.
508
+ */
509
+
510
+ emit NewPolicy(rm, newPolicy_);
511
+ emit PolicyReplaced(rm, oldPolicy.id, newPolicy_.id);
512
+ return newPolicy_.id;
513
+ }
514
+
477
515
  function _validatePolicy(Policy.PolicyData memory policy) internal view {
478
516
  require(policy.id != 0 && policy.hash() == _policies[policy.id], "Policy not found");
479
517
  }
480
518
 
519
+ function makePolicyId(IRiskModule rm, uint96 internalId) public pure returns (uint256) {
520
+ return (uint256(uint160(address(rm))) << 96) + internalId;
521
+ }
522
+
481
523
  function expirePolicy(Policy.PolicyData calldata policy) external override whenNotPaused {
482
524
  require(policy.expiration <= block.timestamp, "Policy not expired yet");
483
525
  return _resolvePolicy(policy, 0, true);
@@ -526,21 +568,14 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable, ERC721
526
568
  IRiskModule rm = policy.riskModule;
527
569
  require(expired || address(rm) == _msgSender(), "Only the RM can resolve policies");
528
570
  require(payout == 0 || policy.expiration > block.timestamp, "Can't pay expired policy");
529
- ComponentStatus compStatus = _rmStatus(rm);
530
- require(
531
- compStatus == ComponentStatus.active || compStatus == ComponentStatus.deprecated,
532
- "Module must be active or deprecated to process resolutions"
533
- );
571
+ _requireCompActiveOrDeprecated(address(rm), ComponentKind.riskModule);
572
+
534
573
  require(payout <= policy.payout, "payout > policy.payout");
535
574
 
536
575
  bool customerWon = payout > 0;
537
576
 
538
577
  IPremiumsAccount pa = rm.premiumsAccount();
539
- compStatus = _paStatus(pa);
540
- require(
541
- compStatus == ComponentStatus.active || compStatus == ComponentStatus.deprecated,
542
- "PremiumsAccount must be active or deprecated to process resolutions"
543
- );
578
+ _requireCompActiveOrDeprecated(address(pa), ComponentKind.premiumsAccount);
544
579
  // Effects
545
580
  delete _policies[policy.id];
546
581
  // Interactions
@@ -2,6 +2,7 @@
2
2
  pragma solidity ^0.8.0;
3
3
 
4
4
  import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
5
+ import {SignedMath} from "@openzeppelin/contracts/utils/math/SignedMath.sol";
5
6
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6
7
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
8
  import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
@@ -476,6 +477,30 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
476
477
  if (policy.srScr > 0) _seniorEtk.lockScr(policy.srScr, policy.srInterestRate());
477
478
  }
478
479
 
480
+ function policyReplaced(
481
+ Policy.PolicyData memory oldPolicy,
482
+ Policy.PolicyData memory newPolicy
483
+ ) external override onlyPolicyPool whenNotPaused {
484
+ if (oldPolicy.srScr > 0 && newPolicy.srScr > 0) {
485
+ int256 diff = int256(oldPolicy.srInterestRate()) - int256(newPolicy.srInterestRate());
486
+ require(SignedMath.abs(diff) < 1e14, "Interest rate can't change");
487
+ }
488
+ if (oldPolicy.jrScr > 0 && newPolicy.jrScr > 0) {
489
+ int256 diff = int256(oldPolicy.jrInterestRate()) - int256(newPolicy.jrInterestRate());
490
+ require(SignedMath.abs(diff) < 1e14, "Interest rate can't change");
491
+ }
492
+ /*
493
+ * Supporting interest rate change is possible, but it would require complex computations.
494
+ * If new IR > old IR, then we must adjust positivelly to accrue the interests not accrued
495
+ * If new IR < old IR, then we must adjust negativelly to substract the interests accrued in excess
496
+ */
497
+ _activePurePremiums += newPolicy.purePremium - oldPolicy.purePremium;
498
+ if (oldPolicy.jrScr > 0) _juniorEtk.unlockScr(oldPolicy.jrScr, oldPolicy.jrInterestRate(), 0);
499
+ if (oldPolicy.srScr > 0) _seniorEtk.unlockScr(oldPolicy.srScr, oldPolicy.srInterestRate(), 0);
500
+ if (newPolicy.jrScr > 0) _juniorEtk.lockScr(newPolicy.jrScr, newPolicy.jrInterestRate());
501
+ if (newPolicy.srScr > 0) _seniorEtk.lockScr(newPolicy.srScr, newPolicy.srInterestRate());
502
+ }
503
+
479
504
  function policyResolvedWithPayout(
480
505
  address policyHolder,
481
506
  Policy.PolicyData memory policy,
@@ -333,7 +333,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
333
333
  address onBehalfOf,
334
334
  uint96 internalId,
335
335
  Params memory params_
336
- ) internal returns (Policy.PolicyData memory) {
336
+ ) internal returns (Policy.PolicyData memory policy) {
337
337
  if (premium == type(uint256).max) {
338
338
  premium = _getMinimumPremium(payout, lossProb, expiration, params_);
339
339
  }
@@ -351,11 +351,60 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
351
351
  "Payer must allow caller to transfer the premium"
352
352
  );
353
353
  require(payout <= maxPayoutPerPolicy(), "RiskModule: Payout is more than maximum per policy");
354
- Policy.PolicyData memory policy = Policy.initialize(this, params_, premium, payout, lossProb, expiration);
354
+ policy = Policy.initialize(this, params_, premium, payout, lossProb, expiration, now_);
355
355
  _activeExposure += policy.payout;
356
356
  require(_activeExposure <= exposureLimit(), "RiskModule: Exposure limit exceeded");
357
- uint256 policyId = _policyPool.newPolicy(policy, payer, onBehalfOf, internalId);
358
- policy.id = policyId;
357
+ policy.id = _policyPool.newPolicy(policy, payer, onBehalfOf, internalId);
358
+ return policy;
359
+ }
360
+
361
+ /**
362
+ * @dev Called from child contracts to replace policies (after they validated the pricing).
363
+ * whenNotPaused validation must be done in the external method.
364
+ *
365
+ * @param payout The exposure (maximum payout) of the policy
366
+ * @param premium The premium that will be paid by the policyHolder
367
+ * @param lossProb The probability of having to pay the maximum payout (wad)
368
+ * @param payer The account that pays for the premium
369
+ * @param expiration The expiration of the policy (timestamp)
370
+ * @param internalId An id that's unique within this module and it will be used to identify the policy
371
+ * @param params_ Params to use to create the new policy
372
+ */
373
+ function _replacePolicy(
374
+ Policy.PolicyData calldata oldPolicy,
375
+ uint256 payout,
376
+ uint256 premium,
377
+ uint256 lossProb,
378
+ uint40 expiration,
379
+ address payer,
380
+ uint96 internalId,
381
+ Params memory params_
382
+ ) internal virtual returns (Policy.PolicyData memory policy) {
383
+ if (premium == type(uint256).max) {
384
+ premium = _getMinimumPremium(payout, lossProb, expiration, params_);
385
+ }
386
+ require(premium < payout, "Premium must be less than payout");
387
+ require(oldPolicy.expiration > uint40(block.timestamp), "Old policy is expired");
388
+ require(
389
+ expiration >= oldPolicy.expiration && payout >= oldPolicy.payout && premium >= oldPolicy.premium,
390
+ "Policy replacement must be greater or equal than old policy"
391
+ );
392
+ require(((expiration - oldPolicy.start) / 3600) < _params.maxDuration, "Policy exceeds max duration");
393
+ require(
394
+ _policyPool.currency().allowance(payer, address(_policyPool)) >= (premium - oldPolicy.premium),
395
+ "You must allow ENSURO to transfer the premium"
396
+ );
397
+ require(
398
+ payer == _msgSender() || _policyPool.currency().allowance(payer, _msgSender()) >= (premium - oldPolicy.premium),
399
+ "Payer must allow caller to transfer the premium"
400
+ );
401
+ require(payout <= maxPayoutPerPolicy(), "RiskModule: Payout is more than maximum per policy");
402
+ policy = Policy.initialize(this, params_, premium, payout, lossProb, expiration, oldPolicy.start);
403
+
404
+ _activeExposure += policy.payout - oldPolicy.payout;
405
+ require(_activeExposure <= exposureLimit(), "RiskModule: Exposure limit exceeded");
406
+
407
+ policy.id = _policyPool.replacePolicy(oldPolicy, policy, payer, internalId);
359
408
  return policy;
360
409
  }
361
410
 
@@ -5,6 +5,7 @@ import {IPolicyPool} from "./interfaces/IPolicyPool.sol";
5
5
  import {IPremiumsAccount} from "./interfaces/IPremiumsAccount.sol";
6
6
  import {RiskModule} from "./RiskModule.sol";
7
7
  import {Policy} from "./Policy.sol";
8
+ import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
8
9
 
9
10
  /**
10
11
  * @title Trustful Risk Module
@@ -17,6 +18,7 @@ import {Policy} from "./Policy.sol";
17
18
  contract TrustfulRiskModule is RiskModule {
18
19
  bytes32 public constant PRICER_ROLE = keccak256("PRICER_ROLE");
19
20
  bytes32 public constant RESOLVER_ROLE = keccak256("RESOLVER_ROLE");
21
+ bytes32 public constant REPLACER_ROLE = keccak256("REPLACER_ROLE");
20
22
 
21
23
  /// @custom:oz-upgrades-unsafe-allow constructor
22
24
  // solhint-disable-next-line no-empty-blocks
@@ -100,6 +102,44 @@ contract TrustfulRiskModule is RiskModule {
100
102
  return _newPolicy(payout, premium, lossProb, expiration, _getPayer(onBehalfOf, premium), onBehalfOf, internalId);
101
103
  }
102
104
 
105
+ /**
106
+ * @dev Replace a policy with a new one, reusing the premium and the capital locked
107
+ *
108
+ * Requirements:
109
+ * - The caller has been granted componentRole(PRICER_ROLE)
110
+ *
111
+ * Emits:
112
+ * - {PolicyPool.PolicyReplaced}
113
+ *
114
+ * @param payout The exposure (maximum payout) of the policy
115
+ * @param premium The premium that will be paid by the policyHolder
116
+ * @param lossProb The probability of having to pay the maximum payout (wad)
117
+ * @param expiration The expiration of the policy (timestamp)
118
+ * @param internalId An id that's unique within this module and it will be used to identify the policy
119
+ * @return Returns the id of the created policy
120
+ */
121
+ function replacePolicy(
122
+ Policy.PolicyData calldata oldPolicy,
123
+ uint256 payout,
124
+ uint256 premium,
125
+ uint256 lossProb,
126
+ uint40 expiration,
127
+ uint96 internalId
128
+ ) external whenNotPaused onlyComponentRole(REPLACER_ROLE) returns (uint256) {
129
+ address onBehalfOf = IERC721(address(_policyPool)).ownerOf(oldPolicy.id);
130
+ return
131
+ _replacePolicy(
132
+ oldPolicy,
133
+ payout,
134
+ premium,
135
+ lossProb,
136
+ expiration,
137
+ _getPayer(onBehalfOf, premium),
138
+ internalId,
139
+ params()
140
+ ).id;
141
+ }
142
+
103
143
  function _getPayer(address onBehalfOf, uint256 premium) internal view returns (address payer) {
104
144
  payer = onBehalfOf;
105
145
  if (payer != _msgSender() && _policyPool.currency().allowance(payer, _msgSender()) < premium)
@@ -4,9 +4,39 @@ pragma solidity ^0.8.0;
4
4
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
5
  import {Policy} from "../Policy.sol";
6
6
  import {IEToken} from "./IEToken.sol";
7
+ import {IRiskModule} from "./IRiskModule.sol";
7
8
  import {IAccessManager} from "./IAccessManager.sol";
8
9
 
9
10
  interface IPolicyPool {
11
+ /**
12
+ * @dev Event emitted every time a new policy is added to the pool. Contains all the data about the policy that is
13
+ * later required for doing operations with the policy like resolution or expiration.
14
+ *
15
+ * @param riskModule The risk module that created the policy
16
+ * @param policy The {Policy-PolicyData} struct with all the immutable fields of the policy.
17
+ */
18
+ event NewPolicy(IRiskModule indexed riskModule, Policy.PolicyData policy);
19
+
20
+ /**
21
+ * @dev Event emitted every time a new policy replaces an old Policy Contains all the data about the policy that is
22
+ * later required for doing operations with the policy like resolution or expiration.
23
+ *
24
+ * @param riskModule The risk module that created the policy
25
+ * @param oldPolicyId The id of the replaced policy.
26
+ * @param newPolicyId The id of the new policy.
27
+ */
28
+ event PolicyReplaced(IRiskModule indexed riskModule, uint256 indexed oldPolicyId, uint256 indexed newPolicyId);
29
+
30
+ /**
31
+ * @dev Event emitted every time a policy is removed from the pool. If the policy expired, the `payout` is 0,
32
+ * otherwise is the amount transferred to the policyholder.
33
+ *
34
+ * @param riskModule The risk module where that created the policy initially.
35
+ * @param policyId The unique id of the policy
36
+ * @param payout The payout that has been paid to the policy holder. 0 when the policy expired.
37
+ */
38
+ event PolicyResolved(IRiskModule indexed riskModule, uint256 indexed policyId, uint256 payout);
39
+
10
40
  /**
11
41
  * @dev Reference to the main currency (ERC20) used in the protocol
12
42
  * @return The address of the currency (e.g. USDC) token used in the protocol
@@ -39,18 +69,44 @@ interface IPolicyPool {
39
69
  * (see Premium Split in the docs)
40
70
  *
41
71
  * @param policy A policy created with {Policy-initialize}
42
- * @param caller The pricer that's creating the policy and will pay for the premium
43
- * @param policyHolder The address of the policy holder and the payer of the premiums
72
+ * @param payer The address that will pay for the premium
73
+ * @param policyHolder The address of the policy holder
44
74
  * @param internalId A unique id within the RiskModule, that will be used to compute the policy id
45
75
  * @return The policy id, identifying the NFT and the policy
46
76
  */
47
77
  function newPolicy(
48
78
  Policy.PolicyData memory policy,
49
- address caller,
79
+ address payer,
50
80
  address policyHolder,
51
81
  uint96 internalId
52
82
  ) external returns (uint256);
53
83
 
84
+ /**
85
+ * @dev Replaces a policy with another. Must be called from an active RiskModule
86
+ *
87
+ * Requirements:
88
+ * - `msg.sender` must be an active RiskModule
89
+ * - `caller` approved the spending of `currency()` for at least `newPolicy_.premium - oldPolicy.premium`
90
+ * - `internalId` must be unique within `policy.riskModule` and not used before
91
+ *
92
+ * Events:
93
+ * - {PolicyPool-PolicyReplaced}: with all the details about the policy
94
+ * - {ERC20-Transfer}: does several transfers from caller address to the different receivers of the premium
95
+ * (see Premium Split in the docs)
96
+ *
97
+ * @param oldPolicy A policy created previously and not expired
98
+ * @param newPolicy_ A policy created with {Policy-initialize}
99
+ * @param payer The address that will pay for the premium
100
+ * @param internalId A unique id within the RiskModule, that will be used to compute the policy id
101
+ * @return The policy id, identifying the NFT and the policy
102
+ */
103
+ function replacePolicy(
104
+ Policy.PolicyData memory oldPolicy,
105
+ Policy.PolicyData memory newPolicy_,
106
+ address payer,
107
+ uint96 internalId
108
+ ) external returns (uint256);
109
+
54
110
  /**
55
111
  * @dev Resolves a policy with a payout. Must be called from an active RiskModule
56
112
  *
@@ -24,6 +24,22 @@ interface IPremiumsAccount {
24
24
  */
25
25
  function policyCreated(Policy.PolicyData memory policy) external;
26
26
 
27
+ /**
28
+ * @dev Replaces a policy with another in PremiumsAccount. Stores the pure premiums difference and
29
+ * re-locks the aditional funds from junior and senior eTokens.
30
+ *
31
+ * Requirements:
32
+ * - Must be called by `policyPool()`
33
+ *
34
+ * Events:
35
+ * - {EToken-SCRUnlocked}
36
+ * - {EToken-SCRLocked}
37
+ *
38
+ * @param oldPolicy The policy to replace (created in a previous transaction)
39
+ * @param newPolicy The policy that will replace the old one (created in this transaction)
40
+ */
41
+ function policyReplaced(Policy.PolicyData memory oldPolicy, Policy.PolicyData memory newPolicy) external;
42
+
27
43
  /**
28
44
  * @dev The PremiumsAccount is notified that the policy was resolved and issues the payout to the policyHolder.
29
45
  *
@@ -10,6 +10,7 @@ import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
10
10
  import {IPolicyPoolComponent} from "../interfaces/IPolicyPoolComponent.sol";
11
11
  import {IEToken} from "../interfaces/IEToken.sol";
12
12
  import {IRiskModule} from "../interfaces/IRiskModule.sol";
13
+ import {IPolicyHolder} from "../interfaces/IPolicyHolder.sol";
13
14
  import {IPremiumsAccount} from "../interfaces/IPremiumsAccount.sol";
14
15
  import {ILPWhitelist} from "../interfaces/ILPWhitelist.sol";
15
16
  import {IAccessManager} from "../interfaces/IAccessManager.sol";
@@ -29,4 +30,5 @@ contract InterfaceIdCalculator {
29
30
  bytes4 public constant ILPWHITELIST_INTERFACEID = type(ILPWhitelist).interfaceId;
30
31
  bytes4 public constant IACCESSMANAGER_INTERFACEID = type(IAccessManager).interfaceId;
31
32
  bytes4 public constant IASSETMANAGER_INTERFACEID = type(IAssetManager).interfaceId;
33
+ bytes4 public constant IPOLICYHOLDER_INTERFACEID = type(IPolicyHolder).interfaceId;
32
34
  }
@@ -20,9 +20,6 @@ contract PolicyPoolMock is IPolicyPool {
20
20
  mapping(uint256 => Policy.PolicyData) internal policies;
21
21
  mapping(uint256 => bytes32) internal policyHashes;
22
22
 
23
- event NewPolicy(IRiskModule indexed riskModule, Policy.PolicyData policy);
24
- event PolicyResolved(IRiskModule indexed riskModule, uint256 indexed policyId, uint256 payout);
25
-
26
23
  constructor(IERC20Metadata currency_, IAccessManager access_) {
27
24
  _currency = currency_;
28
25
  _access = access_;
@@ -42,7 +39,7 @@ contract PolicyPoolMock is IPolicyPool {
42
39
 
43
40
  function newPolicy(
44
41
  Policy.PolicyData memory policy,
45
- address /* caller */,
42
+ address /* payer */,
46
43
  address /* policyHolder */,
47
44
  uint96 internalId
48
45
  ) external override returns (uint256) {
@@ -52,6 +49,20 @@ contract PolicyPoolMock is IPolicyPool {
52
49
  return policy.id;
53
50
  }
54
51
 
52
+ function replacePolicy(
53
+ Policy.PolicyData memory oldPolicy,
54
+ Policy.PolicyData memory newPolicy_,
55
+ address /* payer */,
56
+ uint96 internalId
57
+ ) external override returns (uint256) {
58
+ newPolicy_.id = (uint256(uint160(address(newPolicy_.riskModule))) << 96) + internalId;
59
+ policyHashes[newPolicy_.id] = newPolicy_.hash();
60
+ IRiskModule rm = oldPolicy.riskModule;
61
+ emit NewPolicy(rm, newPolicy_);
62
+ emit PolicyReplaced(IRiskModule(msg.sender), oldPolicy.id, newPolicy_.id);
63
+ return newPolicy_.id;
64
+ }
65
+
55
66
  function _resolvePolicy(Policy.PolicyData memory policy, uint256 payout) internal {
56
67
  require(policy.id != 0, "Policy not found");
57
68
  require(policy.hash() == policyHashes[policy.id], "Hash doesn't match");
@@ -98,9 +109,18 @@ contract PolicyPoolMock is IPolicyPool {
98
109
  uint256 premium,
99
110
  uint256 payout,
100
111
  uint256 lossProb,
101
- uint40 expiration
112
+ uint40 expiration,
113
+ uint40 start
102
114
  ) external {
103
- Policy.PolicyData memory policy = Policy.initialize(riskModule, rmParams, premium, payout, lossProb, expiration);
115
+ Policy.PolicyData memory policy = Policy.initialize(
116
+ riskModule,
117
+ rmParams,
118
+ premium,
119
+ payout,
120
+ lossProb,
121
+ expiration,
122
+ start == 0 ? uint40(block.timestamp) : start
123
+ );
104
124
 
105
125
  emit NewPolicy(riskModule, policy);
106
126
  }
@@ -5,6 +5,7 @@ import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
5
5
  import {IPremiumsAccount} from "../interfaces/IPremiumsAccount.sol";
6
6
  import {RiskModule} from "../RiskModule.sol";
7
7
  import {Policy} from "../Policy.sol";
8
+ import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
8
9
 
9
10
  /**
10
11
  * @title Trustful Risk Module
@@ -17,6 +18,7 @@ import {Policy} from "../Policy.sol";
17
18
  contract RiskModuleMock is RiskModule {
18
19
  bytes32 public constant PRICER_ROLE = keccak256("PRICER_ROLE");
19
20
  bytes32 public constant RESOLVER_ROLE = keccak256("RESOLVER_ROLE");
21
+ bytes32 public constant REPLACER_ROLE = keccak256("REPLACER_ROLE");
20
22
 
21
23
  /// @custom:oz-upgrades-unsafe-allow constructor
22
24
  // solhint-disable-next-line no-empty-blocks
@@ -56,7 +58,41 @@ contract RiskModuleMock is RiskModule {
56
58
  return _newPolicy(payout, premium, lossProb, expiration, payer, onBehalfOf, internalId).id;
57
59
  }
58
60
 
61
+ function newPolicyRaw(
62
+ Policy.PolicyData memory policy,
63
+ address payer,
64
+ address policyHolder,
65
+ uint96 internalId
66
+ ) external returns (uint256) {
67
+ return _policyPool.newPolicy(policy, payer, policyHolder, internalId);
68
+ }
69
+
59
70
  function resolvePolicy(Policy.PolicyData calldata policy, uint256 payout) external onlyComponentRole(RESOLVER_ROLE) {
60
71
  _policyPool.resolvePolicy(policy, payout);
61
72
  }
73
+
74
+ function resolvePolicyRaw(Policy.PolicyData calldata policy, uint256 payout) external {
75
+ return _policyPool.resolvePolicy(policy, payout);
76
+ }
77
+
78
+ function replacePolicy(
79
+ Policy.PolicyData calldata oldPolicy,
80
+ uint256 payout,
81
+ uint256 premium,
82
+ uint256 lossProb,
83
+ uint40 expiration,
84
+ uint96 internalId
85
+ ) external whenNotPaused onlyComponentRole(REPLACER_ROLE) returns (uint256) {
86
+ address onBehalfOf = IERC721(address(_policyPool)).ownerOf(oldPolicy.id);
87
+ return _replacePolicy(oldPolicy, payout, premium, lossProb, expiration, onBehalfOf, internalId, params()).id;
88
+ }
89
+
90
+ function replacePolicyRaw(
91
+ Policy.PolicyData memory oldPolicy,
92
+ Policy.PolicyData memory newPolicy_,
93
+ address payer,
94
+ uint96 internalId
95
+ ) external returns (uint256) {
96
+ return _policyPool.replacePolicy(oldPolicy, newPolicy_, payer, internalId);
97
+ }
62
98
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ensuro/core",
3
3
  "description": "Ensuro - Decentralized insurance protocol",
4
- "version": "2.8.0-beta1",
4
+ "version": "2.8.0-beta2",
5
5
  "files": [
6
6
  "**/*.sol",
7
7
  "/build/contracts/*.json",