@ensuro/core 1.2.0 → 1.2.3
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/build/contracts/AaveAssetManager.sol/AaveAssetManager.json +2 -51
- package/build/contracts/DataTypes.sol/DataTypes.json +2 -2
- package/build/contracts/EToken.sol/EToken.json +2 -2
- package/build/contracts/Exchange.sol/Exchange.json +483 -0
- package/build/contracts/FlightDelayRiskModule.sol/FlightDelayRiskModule.json +2 -2
- package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +2 -2
- package/build/contracts/PolicyNFT.sol/PolicyNFT.json +2 -2
- package/build/contracts/PolicyPool.sol/PolicyPool.json +2 -90
- package/build/contracts/PolicyPoolConfig.sol/PolicyPoolConfig.json +28 -2
- package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +2 -2
- package/build/contracts/extras/IPriceRiskModule.sol/IPriceRiskModule.json +253 -0
- package/build/contracts/extras/PriceRiskModule.sol/PriceRiskModule.json +1036 -0
- package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1.json +2 -2
- package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1_Upgrade.json +2 -2
- package/build/interfaces/IEToken.sol/IEToken.json +26 -0
- package/build/interfaces/IExchange.sol/IExchange.json +153 -0
- package/build/interfaces/IExchange.sol/IPriceOracle.json +30 -0
- package/build/interfaces/IPolicyHolder.sol/IPolicyHolder.json +108 -0
- package/build/interfaces/IPolicyPoolConfig.sol/IPolicyPoolConfig.json +13 -0
- package/contracts/AaveAssetManager.sol +35 -63
- package/contracts/BaseAssetManager.sol +55 -37
- package/contracts/EToken.sol +3 -19
- package/contracts/Exchange.sol +142 -0
- package/contracts/PolicyPool.sol +41 -35
- package/contracts/PolicyPoolComponent.sol +4 -0
- package/contracts/PolicyPoolConfig.sol +16 -0
- package/contracts/RiskModule.sol +5 -26
- package/contracts/extras/IPriceRiskModule.sol +41 -0
- package/contracts/extras/PriceRiskModule.sol +243 -0
- package/interfaces/IExchange.sol +47 -0
- package/interfaces/IPolicyHolder.sol +42 -0
- package/interfaces/IPolicyPoolConfig.sol +4 -0
- package/package.json +1 -1
package/contracts/PolicyPool.sol
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
4
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
5
|
+
import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
|
|
5
6
|
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
7
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
8
|
import {IPolicyPoolConfig} from "../interfaces/IPolicyPoolConfig.sol";
|
|
@@ -13,6 +14,7 @@ import {IInsolvencyHook} from "../interfaces/IInsolvencyHook.sol";
|
|
|
13
14
|
import {IPolicyPoolComponent} from "../interfaces/IPolicyPoolComponent.sol";
|
|
14
15
|
import {IEToken} from "../interfaces/IEToken.sol";
|
|
15
16
|
import {IPolicyNFT} from "../interfaces/IPolicyNFT.sol";
|
|
17
|
+
import {IPolicyHolder} from "../interfaces/IPolicyHolder.sol";
|
|
16
18
|
import {IAssetManager} from "../interfaces/IAssetManager.sol";
|
|
17
19
|
import {Policy} from "./Policy.sol";
|
|
18
20
|
import {WadRayMath} from "./WadRayMath.sol";
|
|
@@ -44,7 +46,6 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable {
|
|
|
44
46
|
// solhint-disable-next-line var-name-mixedcase
|
|
45
47
|
uint256 public immutable NEGLIGIBLE_AMOUNT; // init as 10**(decimals-3) == 0.001 USD
|
|
46
48
|
|
|
47
|
-
bytes32 public constant REBALANCE_ROLE = keccak256("REBALANCE_ROLE");
|
|
48
49
|
bytes32 public constant WITHDRAW_WON_PREMIUMS_ROLE = keccak256("WITHDRAW_WON_PREMIUMS_ROLE");
|
|
49
50
|
|
|
50
51
|
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
|
|
@@ -464,6 +465,45 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable {
|
|
|
464
465
|
emit PolicyResolved(policy.riskModule, policy.id, payout);
|
|
465
466
|
delete _policies[policy.id];
|
|
466
467
|
delete _policiesFunds[policy.id];
|
|
468
|
+
if (payout > 0) {
|
|
469
|
+
_notifyPayout(policy.id, payout);
|
|
470
|
+
} else {
|
|
471
|
+
_notifyExpiration(policy.id);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function _notifyPayout(uint256 policyId, uint256 payout) internal {
|
|
476
|
+
address customer = _policyNFT.ownerOf(policyId);
|
|
477
|
+
if (!AddressUpgradeable.isContract(customer)) return;
|
|
478
|
+
try
|
|
479
|
+
IPolicyHolder(customer).onPayoutReceived(_msgSender(), address(this), policyId, payout)
|
|
480
|
+
returns (bytes4 retval) {
|
|
481
|
+
require(
|
|
482
|
+
retval == IPolicyHolder.onPayoutReceived.selector,
|
|
483
|
+
"Invalid return value from Policy Holder"
|
|
484
|
+
);
|
|
485
|
+
} catch (bytes memory reason) {
|
|
486
|
+
if (reason.length == 0) {
|
|
487
|
+
return; // Not implemented, it's fine
|
|
488
|
+
} else {
|
|
489
|
+
// solhint-disable-next-line no-inline-assembly
|
|
490
|
+
assembly {
|
|
491
|
+
revert(add(32, reason), mload(reason))
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function _notifyExpiration(uint256 policyId) internal {
|
|
498
|
+
address customer = _policyNFT.ownerOf(policyId);
|
|
499
|
+
if (!AddressUpgradeable.isContract(customer)) return;
|
|
500
|
+
try IPolicyHolder(customer).onPolicyExpired(_msgSender(), address(this), policyId) returns (
|
|
501
|
+
bytes4
|
|
502
|
+
) {
|
|
503
|
+
return;
|
|
504
|
+
} catch {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
467
507
|
}
|
|
468
508
|
|
|
469
509
|
function _interestAdjustment(Policy.PolicyData memory policy)
|
|
@@ -609,40 +649,6 @@ contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable {
|
|
|
609
649
|
return amount;
|
|
610
650
|
}
|
|
611
651
|
|
|
612
|
-
function rebalancePolicy(Policy.PolicyData calldata policy)
|
|
613
|
-
external
|
|
614
|
-
onlyRole(REBALANCE_ROLE)
|
|
615
|
-
whenNotPaused
|
|
616
|
-
{
|
|
617
|
-
_validatePolicy(policy);
|
|
618
|
-
DataTypes.ETokenToWadMap storage policyFunds = _policiesFunds[policy.id];
|
|
619
|
-
uint256 ocean = 0;
|
|
620
|
-
|
|
621
|
-
// Iterates all the tokens
|
|
622
|
-
// If locked - unlocks - finally stores the available ocean in policyFunds
|
|
623
|
-
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
624
|
-
(IEToken etk, DataTypes.ETokenStatus etkStatus) = _eTokens.at(i);
|
|
625
|
-
uint256 etkOcean = 0;
|
|
626
|
-
(bool locked, uint256 etkScr) = policyFunds.tryGet(etk);
|
|
627
|
-
if (locked) {
|
|
628
|
-
etk.unlockScr(policy.interestRate(), etkScr);
|
|
629
|
-
}
|
|
630
|
-
if (
|
|
631
|
-
etkStatus == DataTypes.ETokenStatus.active &&
|
|
632
|
-
etk.accepts(address(policy.riskModule), policy.expiration)
|
|
633
|
-
) etkOcean = etk.oceanForNewScr();
|
|
634
|
-
if (etkOcean == 0) {
|
|
635
|
-
if (locked) policyFunds.remove(etk);
|
|
636
|
-
} else {
|
|
637
|
-
policyFunds.set(etk, etkOcean);
|
|
638
|
-
ocean += etkOcean;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
_distributeScr(policy.scr, policy.interestRate(), ocean, policyFunds);
|
|
643
|
-
emit PolicyRebalanced(policy.riskModule, policy.id);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
652
|
function getInvestable() external view override returns (uint256) {
|
|
647
653
|
uint256 borrowedFromEtk = 0;
|
|
648
654
|
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
@@ -134,11 +134,15 @@ abstract contract PolicyPoolComponent is
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// solhint-disable-next-line no-empty-blocks
|
|
138
|
+
function _validateParameters() internal view virtual {} // Must be reimplemented with specific validations
|
|
139
|
+
|
|
137
140
|
function _parameterChanged(
|
|
138
141
|
IPolicyPoolConfig.GovernanceActions action,
|
|
139
142
|
uint256 value,
|
|
140
143
|
bool tweak
|
|
141
144
|
) internal {
|
|
145
|
+
_validateParameters();
|
|
142
146
|
if (tweak) _registerTweak(action);
|
|
143
147
|
emit GovernanceAction(action, value);
|
|
144
148
|
}
|
|
@@ -10,6 +10,7 @@ import {IPolicyPoolConfig} from "../interfaces/IPolicyPoolConfig.sol";
|
|
|
10
10
|
import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
|
|
11
11
|
import {IRiskModule} from "../interfaces/IRiskModule.sol";
|
|
12
12
|
import {ILPWhitelist} from "../interfaces/ILPWhitelist.sol";
|
|
13
|
+
import {IExchange} from "../interfaces/IExchange.sol";
|
|
13
14
|
import {IPolicyPoolComponent} from "../interfaces/IPolicyPoolComponent.sol";
|
|
14
15
|
import {WadRayMath} from "./WadRayMath.sol";
|
|
15
16
|
|
|
@@ -40,6 +41,7 @@ contract PolicyPoolConfig is
|
|
|
40
41
|
IInsolvencyHook internal _insolvencyHook; // Contract that handles insolvency situations
|
|
41
42
|
IPolicyPool internal _policyPool;
|
|
42
43
|
ILPWhitelist internal _lpWhitelist; // Contract that handles whitelisting of Liquidity Providers
|
|
44
|
+
IExchange internal _exchange; // Contract that handles exchange operations between assets
|
|
43
45
|
|
|
44
46
|
mapping(IRiskModule => RiskModuleStatus) private _riskModules;
|
|
45
47
|
|
|
@@ -168,6 +170,20 @@ contract PolicyPoolConfig is
|
|
|
168
170
|
return _lpWhitelist;
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
function setExchange(IExchange exchange_) external onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {
|
|
174
|
+
require(
|
|
175
|
+
address(exchange_) == address(0) ||
|
|
176
|
+
IPolicyPoolComponent(address(exchange_)).policyPool() == _policyPool,
|
|
177
|
+
"Component not linked to this PolicyPool"
|
|
178
|
+
);
|
|
179
|
+
_exchange = exchange_;
|
|
180
|
+
emit ComponentChanged(GovernanceActions.setExchange, address(_exchange));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function exchange() external view override returns (IExchange) {
|
|
184
|
+
return _exchange;
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
function addRiskModule(IRiskModule riskModule) external onlyRole2(LEVEL1_ROLE, LEVEL2_ROLE) {
|
|
172
188
|
require(
|
|
173
189
|
_riskModules[riskModule] == RiskModuleStatus.inactive,
|
package/contracts/RiskModule.sol
CHANGED
|
@@ -37,11 +37,6 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
37
37
|
|
|
38
38
|
address internal _wallet; // Address of the RiskModule provider
|
|
39
39
|
|
|
40
|
-
modifier validateParamsAfterChange() {
|
|
41
|
-
_;
|
|
42
|
-
_validateParameters();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
40
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
46
41
|
// solhint-disable-next-line no-empty-blocks
|
|
47
42
|
constructor(IPolicyPool policyPool_) PolicyPoolComponent(policyPool_) {}
|
|
@@ -104,7 +99,7 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
// runs validation on RiskModule parameters
|
|
107
|
-
function _validateParameters() internal view {
|
|
102
|
+
function _validateParameters() internal view override {
|
|
108
103
|
require(
|
|
109
104
|
_scrPercentage <= WadRayMath.RAY && _scrPercentage > 0,
|
|
110
105
|
"Validation: scrPercentage must be <=1"
|
|
@@ -159,7 +154,6 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
159
154
|
function setScrPercentage(uint256 newScrPercentage)
|
|
160
155
|
external
|
|
161
156
|
onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
|
|
162
|
-
validateParamsAfterChange
|
|
163
157
|
{
|
|
164
158
|
bool tweak = !hasPoolRole(LEVEL2_ROLE);
|
|
165
159
|
require(
|
|
@@ -174,11 +168,7 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
174
168
|
);
|
|
175
169
|
}
|
|
176
170
|
|
|
177
|
-
function setMoc(uint256 newMoc)
|
|
178
|
-
external
|
|
179
|
-
onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
|
|
180
|
-
validateParamsAfterChange
|
|
181
|
-
{
|
|
171
|
+
function setMoc(uint256 newMoc) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
|
|
182
172
|
bool tweak = !hasPoolRole(LEVEL2_ROLE);
|
|
183
173
|
require(!tweak || _isTweakRay(_moc, newMoc, 1e26), "Tweak exceeded: moc tweaks only up to 10%");
|
|
184
174
|
_moc = newMoc;
|
|
@@ -188,7 +178,6 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
188
178
|
function setScrInterestRate(uint256 newScrInterestRate)
|
|
189
179
|
external
|
|
190
180
|
onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
|
|
191
|
-
validateParamsAfterChange
|
|
192
181
|
{
|
|
193
182
|
bool tweak = !hasPoolRole(LEVEL2_ROLE);
|
|
194
183
|
require(
|
|
@@ -203,11 +192,7 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
203
192
|
);
|
|
204
193
|
}
|
|
205
194
|
|
|
206
|
-
function setEnsuroFee(uint256 newEnsuroFee)
|
|
207
|
-
external
|
|
208
|
-
onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
|
|
209
|
-
validateParamsAfterChange
|
|
210
|
-
{
|
|
195
|
+
function setEnsuroFee(uint256 newEnsuroFee) external onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE) {
|
|
211
196
|
bool tweak = !hasPoolRole(LEVEL2_ROLE);
|
|
212
197
|
require(
|
|
213
198
|
!tweak || _isTweakRay(_ensuroFee, newEnsuroFee, 3e26),
|
|
@@ -220,7 +205,6 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
220
205
|
function setMaxScrPerPolicy(uint256 newMaxScrPerPolicy)
|
|
221
206
|
external
|
|
222
207
|
onlyPoolRole2(LEVEL2_ROLE, LEVEL3_ROLE)
|
|
223
|
-
validateParamsAfterChange
|
|
224
208
|
{
|
|
225
209
|
bool tweak = !hasPoolRole(LEVEL2_ROLE);
|
|
226
210
|
require(
|
|
@@ -238,7 +222,6 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
238
222
|
function setScrLimit(uint256 newScrLimit)
|
|
239
223
|
external
|
|
240
224
|
onlyPoolRole3(LEVEL1_ROLE, LEVEL2_ROLE, LEVEL3_ROLE)
|
|
241
|
-
validateParamsAfterChange
|
|
242
225
|
{
|
|
243
226
|
bool tweak = !hasPoolRole(LEVEL2_ROLE) && !hasPoolRole(LEVEL1_ROLE);
|
|
244
227
|
require(
|
|
@@ -256,11 +239,7 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
256
239
|
_parameterChanged(IPolicyPoolConfig.GovernanceActions.setScrLimit, newScrLimit, tweak);
|
|
257
240
|
}
|
|
258
241
|
|
|
259
|
-
function setWallet(address wallet_)
|
|
260
|
-
external
|
|
261
|
-
onlyRole(RM_PROVIDER_ROLE)
|
|
262
|
-
validateParamsAfterChange
|
|
263
|
-
{
|
|
242
|
+
function setWallet(address wallet_) external onlyRole(RM_PROVIDER_ROLE) {
|
|
264
243
|
_wallet = wallet_;
|
|
265
244
|
_parameterChanged(
|
|
266
245
|
IPolicyPoolConfig.GovernanceActions.setWallet,
|
|
@@ -273,7 +252,7 @@ abstract contract RiskModule is IRiskModule, AccessControlUpgradeable, PolicyPoo
|
|
|
273
252
|
uint256 payout,
|
|
274
253
|
uint256 lossProb,
|
|
275
254
|
uint40 expiration
|
|
276
|
-
)
|
|
255
|
+
) public view returns (uint256) {
|
|
277
256
|
uint256 purePremium = payout.wadToRay().rayMul(lossProb.rayMul(moc())).rayToWad();
|
|
278
257
|
uint256 scr = payout.wadMul(scrPercentage().rayToWad()) - purePremium;
|
|
279
258
|
uint256 interestRate = (
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IRiskModule} from "../../interfaces/IRiskModule.sol";
|
|
5
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @title IPriceRiskModule interface
|
|
9
|
+
* @dev Interface for price risk module
|
|
10
|
+
* @author Ensuro
|
|
11
|
+
*/
|
|
12
|
+
interface IPriceRiskModule is IRiskModule {
|
|
13
|
+
/**
|
|
14
|
+
* @dev Returns the premium and lossProb of the policy
|
|
15
|
+
* @param triggerPrice Price of the asset_ that will trigger the policy (expressed in _currency)
|
|
16
|
+
* @param lower If true -> triggers if the price is lower, If false -> triggers if the price is higher
|
|
17
|
+
* @param payout Expressed in policyPool.currency()
|
|
18
|
+
* @param expiration Expiration of the policy
|
|
19
|
+
* @return premium Premium that needs to be paid
|
|
20
|
+
* @return lossProb Probability of paying the maximum payout
|
|
21
|
+
*/
|
|
22
|
+
function pricePolicy(
|
|
23
|
+
uint256 triggerPrice,
|
|
24
|
+
bool lower,
|
|
25
|
+
uint256 payout,
|
|
26
|
+
uint40 expiration
|
|
27
|
+
) external view returns (uint256 premium, uint256 lossProb);
|
|
28
|
+
|
|
29
|
+
function newPolicy(
|
|
30
|
+
uint256 triggerPrice,
|
|
31
|
+
bool lower,
|
|
32
|
+
uint256 payout,
|
|
33
|
+
uint40 expiration
|
|
34
|
+
) external returns (uint256);
|
|
35
|
+
|
|
36
|
+
function triggerPolicy(uint256 policyId) external;
|
|
37
|
+
|
|
38
|
+
function referenceCurrency() external view returns (IERC20Metadata);
|
|
39
|
+
|
|
40
|
+
function asset() external view returns (IERC20Metadata);
|
|
41
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
5
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
6
|
+
import {IPolicyPool} from "../../interfaces/IPolicyPool.sol";
|
|
7
|
+
import {IPolicyPoolConfig} from "../../interfaces/IPolicyPoolConfig.sol";
|
|
8
|
+
import {RiskModule} from "../RiskModule.sol";
|
|
9
|
+
import {Policy} from "../Policy.sol";
|
|
10
|
+
import {WadRayMath} from "../WadRayMath.sol";
|
|
11
|
+
import {IPriceRiskModule} from "./IPriceRiskModule.sol";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @title PriceRiskModule
|
|
15
|
+
* @dev Risk Module that triggers the payout if the price of an asset is lower or higher than trigger price
|
|
16
|
+
* @custom:security-contact security@ensuro.co
|
|
17
|
+
* @author Ensuro
|
|
18
|
+
*/
|
|
19
|
+
contract PriceRiskModule is RiskModule, IPriceRiskModule {
|
|
20
|
+
using SafeERC20 for IERC20Metadata;
|
|
21
|
+
using WadRayMath for uint256;
|
|
22
|
+
|
|
23
|
+
bytes32 public constant CUSTOMER_ROLE = keccak256("CUSTOMER_ROLE");
|
|
24
|
+
bytes32 public constant PRICER_ROLE = keccak256("PRICER_ROLE");
|
|
25
|
+
|
|
26
|
+
uint8 public constant PRICE_SLOTS = 30;
|
|
27
|
+
|
|
28
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
29
|
+
IERC20Metadata internal immutable _asset;
|
|
30
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
31
|
+
IERC20Metadata internal immutable _referenceCurrency;
|
|
32
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
33
|
+
uint256 internal immutable _slotSize;
|
|
34
|
+
|
|
35
|
+
struct PolicyData {
|
|
36
|
+
Policy.PolicyData ensuroPolicy;
|
|
37
|
+
uint256 triggerPrice;
|
|
38
|
+
bool lower; // If true -> triggers if the price is lower, If false -> triggers if the price is higher
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
mapping(uint256 => PolicyData) internal _policies;
|
|
42
|
+
|
|
43
|
+
// Duration (in hours) of the protection * (1 if lower else -1) => cummulative density function
|
|
44
|
+
// [0] = prob of ([0, infinite%)
|
|
45
|
+
// [1] = prob of ([1, infinite%)
|
|
46
|
+
// ...
|
|
47
|
+
// [PRICE_SLOTS - 1] = prob of ([PRICE_SLOTS - 1, -infinite%)
|
|
48
|
+
mapping(int40 => uint256[PRICE_SLOTS]) internal _cdf;
|
|
49
|
+
|
|
50
|
+
uint96 internal _internalId;
|
|
51
|
+
|
|
52
|
+
event NewPricePolicy(
|
|
53
|
+
address indexed customer,
|
|
54
|
+
uint256 policyId,
|
|
55
|
+
uint256 triggerPrice,
|
|
56
|
+
bool lower
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @dev Constructs the LiquidationProtectionRiskModule
|
|
61
|
+
* @param policyPool_ The policyPool
|
|
62
|
+
* @param asset_ Address of the asset which price want to protect
|
|
63
|
+
* @param referenceCurrency_ Address of the comparison asset (price will be price(asset)/price(currency))
|
|
64
|
+
* @param slotSize_ Size of each percentage slot in the pdf function (in wad)
|
|
65
|
+
*/
|
|
66
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
67
|
+
constructor(
|
|
68
|
+
IPolicyPool policyPool_,
|
|
69
|
+
IERC20Metadata asset_,
|
|
70
|
+
IERC20Metadata referenceCurrency_,
|
|
71
|
+
uint256 slotSize_
|
|
72
|
+
) RiskModule(policyPool_) {
|
|
73
|
+
_asset = asset_;
|
|
74
|
+
_referenceCurrency = referenceCurrency_;
|
|
75
|
+
_slotSize = slotSize_;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @dev Initializes the RiskModule
|
|
80
|
+
* @param name_ Name of the Risk Module
|
|
81
|
+
* @param scrPercentage_ Solvency Capital Requirement percentage, to calculate
|
|
82
|
+
* capital requirement as % of (payout - premium) (in ray)
|
|
83
|
+
* @param ensuroFee_ % of premium that will go for Ensuro treasury (in ray)
|
|
84
|
+
* @param scrInterestRate_ cost of capital (in ray)
|
|
85
|
+
* @param maxScrPerPolicy_ Max SCR to be allocated to this module (in wad)
|
|
86
|
+
* @param scrLimit_ Max SCR to be allocated to this module (in wad)
|
|
87
|
+
* @param wallet_ Address of the RiskModule provider
|
|
88
|
+
*/
|
|
89
|
+
function initialize(
|
|
90
|
+
string memory name_,
|
|
91
|
+
uint256 scrPercentage_,
|
|
92
|
+
uint256 ensuroFee_,
|
|
93
|
+
uint256 scrInterestRate_,
|
|
94
|
+
uint256 maxScrPerPolicy_,
|
|
95
|
+
uint256 scrLimit_,
|
|
96
|
+
address wallet_
|
|
97
|
+
) public initializer {
|
|
98
|
+
__RiskModule_init(
|
|
99
|
+
name_,
|
|
100
|
+
scrPercentage_,
|
|
101
|
+
ensuroFee_,
|
|
102
|
+
scrInterestRate_,
|
|
103
|
+
maxScrPerPolicy_,
|
|
104
|
+
scrLimit_,
|
|
105
|
+
wallet_
|
|
106
|
+
);
|
|
107
|
+
_internalId = 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function _getCurrentPrice() internal view returns (uint256) {
|
|
111
|
+
uint256 ret = policyPool().config().exchange().convert(
|
|
112
|
+
address(_asset),
|
|
113
|
+
address(_referenceCurrency),
|
|
114
|
+
10**_asset.decimals()
|
|
115
|
+
);
|
|
116
|
+
require(ret != 0, "Price not available");
|
|
117
|
+
return ret;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @dev Returns the premium and lossProb of the policy
|
|
122
|
+
* @param triggerPrice Price of the asset_ that will trigger the policy (expressed in _referenceCurrency)
|
|
123
|
+
* @param lower If true -> triggers if the price is lower, If false -> triggers if the price is higher
|
|
124
|
+
* @param payout Expressed in policyPool.currency()
|
|
125
|
+
* @param expiration Expiration of the policy
|
|
126
|
+
* @return premium Premium that needs to be paid
|
|
127
|
+
* @return lossProb Probability of paying the maximum payout
|
|
128
|
+
*/
|
|
129
|
+
function pricePolicy(
|
|
130
|
+
uint256 triggerPrice,
|
|
131
|
+
bool lower,
|
|
132
|
+
uint256 payout,
|
|
133
|
+
uint40 expiration
|
|
134
|
+
) public view override returns (uint256 premium, uint256 lossProb) {
|
|
135
|
+
uint256 currentPrice = _getCurrentPrice();
|
|
136
|
+
require(
|
|
137
|
+
(lower && currentPrice > triggerPrice) || (!lower && currentPrice < triggerPrice),
|
|
138
|
+
"Price already at trigger value"
|
|
139
|
+
);
|
|
140
|
+
lossProb = _computeLossProb(currentPrice, triggerPrice, expiration - uint40(block.timestamp));
|
|
141
|
+
if (lossProb == 0) return (0, 0);
|
|
142
|
+
premium = getMinimumPremium(payout, lossProb, expiration); // TODO: extra fee for RiskModule?
|
|
143
|
+
return (premium, lossProb);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function _computeLossProb(
|
|
147
|
+
uint256 currentPrice,
|
|
148
|
+
uint256 triggerPrice,
|
|
149
|
+
uint40 duration
|
|
150
|
+
) internal view returns (uint256) {
|
|
151
|
+
uint256[PRICE_SLOTS] storage pdf = _cdf[
|
|
152
|
+
int40((duration + 1800) / 3600) * (currentPrice > triggerPrice ? int40(1) : int40(-1))
|
|
153
|
+
];
|
|
154
|
+
uint256 priceJump;
|
|
155
|
+
uint256 decimalConv = 10**(18 - _referenceCurrency.decimals());
|
|
156
|
+
// Calculate the jump percentage as integer with simetric rounding
|
|
157
|
+
if (currentPrice > triggerPrice) {
|
|
158
|
+
priceJump =
|
|
159
|
+
WadRayMath.wad() -
|
|
160
|
+
(triggerPrice * decimalConv).wadDiv(currentPrice * decimalConv);
|
|
161
|
+
} else {
|
|
162
|
+
priceJump =
|
|
163
|
+
(triggerPrice * decimalConv).wadDiv(currentPrice * decimalConv) -
|
|
164
|
+
WadRayMath.wad();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
uint8 downPerc = uint8((priceJump + _slotSize / 2) / _slotSize);
|
|
168
|
+
|
|
169
|
+
if (downPerc >= PRICE_SLOTS) {
|
|
170
|
+
return pdf[PRICE_SLOTS - 1];
|
|
171
|
+
} else {
|
|
172
|
+
return pdf[downPerc];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function newPolicy(
|
|
177
|
+
uint256 triggerPrice,
|
|
178
|
+
bool lower,
|
|
179
|
+
uint256 payout,
|
|
180
|
+
uint40 expiration
|
|
181
|
+
) external override returns (uint256) {
|
|
182
|
+
(uint256 premium, uint256 lossProb) = pricePolicy(triggerPrice, lower, payout, expiration);
|
|
183
|
+
require(premium > 0, "Either duration or percentage jump not supported");
|
|
184
|
+
|
|
185
|
+
uint256 policyId = (uint256(uint160(address(this))) << 96) + _internalId;
|
|
186
|
+
PolicyData storage priceRiskPolicy = _policies[policyId];
|
|
187
|
+
address customer = _msgSender();
|
|
188
|
+
priceRiskPolicy.ensuroPolicy = _newPolicy(
|
|
189
|
+
payout,
|
|
190
|
+
premium,
|
|
191
|
+
lossProb,
|
|
192
|
+
expiration,
|
|
193
|
+
customer,
|
|
194
|
+
_internalId
|
|
195
|
+
);
|
|
196
|
+
_internalId += 1;
|
|
197
|
+
priceRiskPolicy.triggerPrice = triggerPrice;
|
|
198
|
+
priceRiskPolicy.lower = lower;
|
|
199
|
+
emit NewPricePolicy(customer, policyId, triggerPrice, lower);
|
|
200
|
+
return policyId;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function triggerPolicy(uint256 policyId) external override whenNotPaused {
|
|
204
|
+
PolicyData storage policy = _policies[policyId];
|
|
205
|
+
uint256 currentPrice = _getCurrentPrice();
|
|
206
|
+
require(
|
|
207
|
+
!policy.lower || currentPrice <= policy.triggerPrice,
|
|
208
|
+
"Condition not met CurrentPrice > triggerPrice"
|
|
209
|
+
);
|
|
210
|
+
require(
|
|
211
|
+
policy.lower || currentPrice >= policy.triggerPrice,
|
|
212
|
+
"Condition not met CurrentPrice < triggerPrice"
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
_policyPool.resolvePolicy(policy.ensuroPolicy, policy.ensuroPolicy.payout);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @dev Sets the probability distribution for a given duration
|
|
220
|
+
* @param duration Duration of the policy in hours (simetric rounding) positive if probability of lower price
|
|
221
|
+
* negative if probability of higher price
|
|
222
|
+
* @param cdf Array where cdf[i] = prob of price lower/higher than i% of current price
|
|
223
|
+
*/
|
|
224
|
+
function setCDF(int40 duration, uint256[PRICE_SLOTS] calldata cdf)
|
|
225
|
+
external
|
|
226
|
+
onlyRole(PRICER_ROLE)
|
|
227
|
+
whenNotPaused
|
|
228
|
+
{
|
|
229
|
+
_cdf[duration] = cdf;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getCDF(int40 duration) external view returns (uint256[PRICE_SLOTS] memory) {
|
|
233
|
+
return _cdf[duration];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function referenceCurrency() external view override returns (IERC20Metadata) {
|
|
237
|
+
return _referenceCurrency;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function asset() external view override returns (IERC20Metadata) {
|
|
241
|
+
return _asset;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title IPriceOracle - Interface for external price oracle to get assets' prices
|
|
6
|
+
* @author Ensuro
|
|
7
|
+
*/
|
|
8
|
+
interface IPriceOracle {
|
|
9
|
+
/**
|
|
10
|
+
* @dev Returns the price of the asset in ETH
|
|
11
|
+
* @param asset Address of a ERC20 asset
|
|
12
|
+
* @return Price of the asset in ETH (Wad)
|
|
13
|
+
*/
|
|
14
|
+
function getAssetPrice(address asset) external view returns (uint256);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @title IExchange - Interface that handles exchange operations between tokens
|
|
19
|
+
* @author Ensuro
|
|
20
|
+
*/
|
|
21
|
+
interface IExchange {
|
|
22
|
+
function convert(
|
|
23
|
+
address assetFrom,
|
|
24
|
+
address assetTo,
|
|
25
|
+
uint256 amount
|
|
26
|
+
) external view returns (uint256);
|
|
27
|
+
|
|
28
|
+
function getAmountIn(
|
|
29
|
+
address assetIn,
|
|
30
|
+
address assetOut,
|
|
31
|
+
uint256 amountOut
|
|
32
|
+
) external view returns (uint256);
|
|
33
|
+
|
|
34
|
+
function getSwapRouter() external view returns (address);
|
|
35
|
+
|
|
36
|
+
function getPriceOracle() external view returns (IPriceOracle);
|
|
37
|
+
|
|
38
|
+
function sell(
|
|
39
|
+
address assetIn,
|
|
40
|
+
address assetOut,
|
|
41
|
+
uint256 amountInExact,
|
|
42
|
+
address outAddr,
|
|
43
|
+
uint256 deadline
|
|
44
|
+
) external view returns (bytes memory);
|
|
45
|
+
|
|
46
|
+
function decodeSwapOut(bytes memory responseData) external view returns (uint256);
|
|
47
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title Policy holder interface
|
|
8
|
+
* @dev Interface for any contract that wants to be a holder of Ensuro Policies and receive the payouts
|
|
9
|
+
*/
|
|
10
|
+
interface IPolicyHolder is IERC721Receiver {
|
|
11
|
+
/**
|
|
12
|
+
* @dev Whenever an Policy is expired or resolved with payout = 0, this function is called
|
|
13
|
+
*
|
|
14
|
+
* It should return its Solidity selector to confirm the payout.
|
|
15
|
+
* If interface is not implemented by the recipient, it will be ignored and the payout will be successful.
|
|
16
|
+
* No mather what's the return value or even if this function reverts, this function will not revert the policy
|
|
17
|
+
* expiration.
|
|
18
|
+
*
|
|
19
|
+
* The selector can be obtained in Solidity with `IPolicyPool.onPolicyExpired.selector`.
|
|
20
|
+
*/
|
|
21
|
+
function onPolicyExpired(
|
|
22
|
+
address operator,
|
|
23
|
+
address from,
|
|
24
|
+
uint256 policyId
|
|
25
|
+
) external returns (bytes4);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @dev Whenever an Policy is resolved with payout > 0, this function is called
|
|
29
|
+
*
|
|
30
|
+
* It must return its Solidity selector to confirm the payout.
|
|
31
|
+
* If interface is not implemented by the recipient, it will be ignored and the payout will be successful.
|
|
32
|
+
* If any other value is returned or it reverts, the policy resolution / payout will be reverted.
|
|
33
|
+
*
|
|
34
|
+
* The selector can be obtained in Solidity with `IPolicyPool.onPayoutReceived.selector`.
|
|
35
|
+
*/
|
|
36
|
+
function onPayoutReceived(
|
|
37
|
+
address operator,
|
|
38
|
+
address from,
|
|
39
|
+
uint256 policyId,
|
|
40
|
+
uint256 amount
|
|
41
|
+
) external returns (bytes4);
|
|
42
|
+
}
|
|
@@ -6,6 +6,7 @@ import {IAssetManager} from "./IAssetManager.sol";
|
|
|
6
6
|
import {IInsolvencyHook} from "./IInsolvencyHook.sol";
|
|
7
7
|
import {ILPWhitelist} from "./ILPWhitelist.sol";
|
|
8
8
|
import {IRiskModule} from "./IRiskModule.sol";
|
|
9
|
+
import {IExchange} from "./IExchange.sol";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @title IPolicyPoolAccess - Interface for the contract that handles roles for the PolicyPool and components
|
|
@@ -45,6 +46,7 @@ interface IPolicyPoolConfig is IAccessControlUpgradeable {
|
|
|
45
46
|
setMaxSlippage,
|
|
46
47
|
setAcceptAllRMs, // EToken Governance action - value == boolean
|
|
47
48
|
setAcceptException, // EToken Governance action - value == (!isException << 255)|address
|
|
49
|
+
setExchange, // Changes exchange helper contract
|
|
48
50
|
last
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -73,6 +75,8 @@ interface IPolicyPoolConfig is IAccessControlUpgradeable {
|
|
|
73
75
|
|
|
74
76
|
function lpWhitelist() external view returns (ILPWhitelist);
|
|
75
77
|
|
|
78
|
+
function exchange() external view returns (IExchange);
|
|
79
|
+
|
|
76
80
|
function treasury() external view returns (address);
|
|
77
81
|
|
|
78
82
|
function checkAcceptsNewPolicy(IRiskModule riskModule) external view;
|