@ensuro/core 2.8.0-beta1 → 2.8.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.
- package/README.md +6 -2
- package/contracts/Policy.sol +4 -3
- package/contracts/PolicyPool.sol +87 -52
- package/contracts/PremiumsAccount.sol +25 -0
- package/contracts/RiskModule.sol +53 -4
- package/contracts/TrustfulRiskModule.sol +40 -0
- package/contracts/interfaces/IPolicyPool.sol +59 -3
- package/contracts/interfaces/IPremiumsAccount.sol +16 -0
- package/contracts/mocks/InterfaceIdCalculator.sol +2 -0
- package/contracts/mocks/PolicyPoolMock.sol +26 -6
- package/contracts/mocks/RiskModuleMock.sol +36 -0
- package/js/utils.js +1 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
|
|
@@ -131,6 +131,10 @@ Also the docker container is prepared to run [hardhat](https://hardhat.org/). Th
|
|
|
131
131
|
|
|
132
132
|
- Audit by [Quantstamp](https://quantstamp.com/) - 2022-09-26 through 2022-10-20: [AuditReport](audits/Quantstamp-Ensuro-Final-Report-2022-11-09.pdf)
|
|
133
133
|
- Audit by [SlowMist](https://www.slowmist.com) - 2021-09-29: [AuditReport](audits/SlowMistAuditReport-Ensuro-2021-09-29.pdf)
|
|
134
|
+
- Process Quality Review by [DefiSafety](https://www.defisafety.com/) - 2024-03-18: [Process Quality Review](audits/DefiSafety.Process_Quality_Review.Ensuro.pdf)
|
|
135
|
+
|
|
136
|
+
[](https://www.defisafety.com/app/pqrs/594)
|
|
137
|
+
|
|
134
138
|
|
|
135
139
|
## Contributing
|
|
136
140
|
|
package/contracts/Policy.sol
CHANGED
|
@@ -40,8 +40,9 @@ library Policy {
|
|
|
40
40
|
uint256 premium,
|
|
41
41
|
uint256 payout,
|
|
42
42
|
uint256 lossProb,
|
|
43
|
-
uint40 expiration
|
|
44
|
-
|
|
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 =
|
|
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
|
package/contracts/PolicyPool.sol
CHANGED
|
@@ -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
|
|
404
|
-
Component storage comp = _components[IPolicyPoolComponent(
|
|
405
|
-
require(comp.kind ==
|
|
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
|
|
410
|
-
Component
|
|
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
|
|
416
|
-
|
|
417
|
-
require(
|
|
418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
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
|
-
|
|
423
|
+
_requireCompActive(address(rm), ComponentKind.riskModule);
|
|
448
424
|
IPremiumsAccount pa = rm.premiumsAccount();
|
|
449
|
-
|
|
425
|
+
_requireCompActive(address(pa), ComponentKind.premiumsAccount);
|
|
450
426
|
|
|
451
427
|
// Effects
|
|
452
|
-
policy.id = (
|
|
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
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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,
|
package/contracts/RiskModule.sol
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
358
|
-
policy
|
|
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
|
|
43
|
-
* @param policyHolder The address of the policy holder
|
|
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
|
|
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 /*
|
|
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(
|
|
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/js/utils.js
CHANGED