@ensuro/core 1.2.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 +93 -0
- package/build/contracts/AaveAssetManager.sol/AaveAssetManager.json +772 -0
- package/build/contracts/BaseAssetManager.sol/BaseAssetManager.json +494 -0
- package/build/contracts/DataTypes.sol/DataTypes.json +10 -0
- package/build/contracts/EToken.sol/EToken.json +1148 -0
- package/build/contracts/FlightDelayRiskModule.sol/FlightDelayRiskModule.json +1093 -0
- package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +412 -0
- package/build/contracts/Policy.sol/Policy.json +10 -0
- package/build/contracts/PolicyNFT.sol/PolicyNFT.json +570 -0
- package/build/contracts/PolicyPool.sol/PolicyPool.json +1257 -0
- package/build/contracts/PolicyPoolComponent.sol/PolicyPoolComponent.json +281 -0
- package/build/contracts/PolicyPoolConfig.sol/PolicyPoolConfig.json +660 -0
- package/build/contracts/RiskModule.sol/RiskModule.json +748 -0
- package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +1032 -0
- package/build/contracts/WadRayMath.sol/WadRayMath.json +10 -0
- package/build/contracts/dependencies/ChainlinkClientUpgradeable.sol/ChainlinkClientUpgradeable.json +50 -0
- package/build/contracts/upgraded/IPolicyNFTv1.sol/IPolicyNFTv1.json +322 -0
- package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1.json +571 -0
- package/build/contracts/upgraded/PolicyNFTv1.sol/PolicyNFTv1_Upgrade.json +602 -0
- package/build/interfaces/IAssetManager.sol/IAssetManager.json +31 -0
- package/build/interfaces/IEToken.sol/IEToken.json +491 -0
- package/build/interfaces/IInsolvencyHook.sol/IInsolvencyHook.json +42 -0
- package/build/interfaces/ILPWhitelist.sol/ILPWhitelist.json +74 -0
- package/build/interfaces/IPolicyNFT.sol/IPolicyNFT.json +321 -0
- package/build/interfaces/IPolicyPool.sol/IPolicyPool.json +445 -0
- package/build/interfaces/IPolicyPoolComponent.sol/IPolicyPoolComponent.json +24 -0
- package/build/interfaces/IPolicyPoolConfig.sol/IPolicyPoolConfig.json +328 -0
- package/build/interfaces/IRiskModule.sol/IRiskModule.json +141 -0
- package/contracts/AaveAssetManager.sol +326 -0
- package/contracts/BaseAssetManager.sol +283 -0
- package/contracts/DataTypes.sol +287 -0
- package/contracts/EToken.sol +773 -0
- package/contracts/FlightDelayRiskModule.sol +208 -0
- package/contracts/LPManualWhitelist.sol +57 -0
- package/contracts/Policy.sol +104 -0
- package/contracts/PolicyNFT.sol +96 -0
- package/contracts/PolicyPool.sol +716 -0
- package/contracts/PolicyPoolComponent.sol +164 -0
- package/contracts/PolicyPoolConfig.sol +231 -0
- package/contracts/RiskModule.sol +323 -0
- package/contracts/TrustfulRiskModule.sol +81 -0
- package/contracts/WadRayMath.sol +135 -0
- package/contracts/dependencies/ChainlinkClientUpgradeable.sol +371 -0
- package/contracts/upgraded/IPolicyNFTv1.sol +15 -0
- package/contracts/upgraded/PolicyNFTv1.sol +126 -0
- package/interfaces/IAssetManager.sol +23 -0
- package/interfaces/IEToken.sol +44 -0
- package/interfaces/IInsolvencyHook.sol +27 -0
- package/interfaces/ILPWhitelist.sol +23 -0
- package/interfaces/IPolicyNFT.sol +15 -0
- package/interfaces/IPolicyPool.sol +44 -0
- package/interfaces/IPolicyPoolComponent.sol +13 -0
- package/interfaces/IPolicyPoolConfig.sol +81 -0
- package/interfaces/IRiskModule.sol +29 -0
- package/package.json +30 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
5
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
|
+
import {IPolicyPoolConfig} from "../interfaces/IPolicyPoolConfig.sol";
|
|
8
|
+
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
|
9
|
+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
10
|
+
import {IPolicyPool} from "../interfaces/IPolicyPool.sol";
|
|
11
|
+
import {IRiskModule} from "../interfaces/IRiskModule.sol";
|
|
12
|
+
import {IInsolvencyHook} from "../interfaces/IInsolvencyHook.sol";
|
|
13
|
+
import {IPolicyPoolComponent} from "../interfaces/IPolicyPoolComponent.sol";
|
|
14
|
+
import {IEToken} from "../interfaces/IEToken.sol";
|
|
15
|
+
import {IPolicyNFT} from "../interfaces/IPolicyNFT.sol";
|
|
16
|
+
import {IAssetManager} from "../interfaces/IAssetManager.sol";
|
|
17
|
+
import {Policy} from "./Policy.sol";
|
|
18
|
+
import {WadRayMath} from "./WadRayMath.sol";
|
|
19
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
20
|
+
import {DataTypes} from "./DataTypes.sol";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @title Ensuro PolicyPool contract
|
|
24
|
+
* @dev This is the main contract of the protocol, it stores the eTokens (liquidity pools) and has the operations
|
|
25
|
+
* to interact with them. This is also the contract that receives and sends the underlying asset.
|
|
26
|
+
* Also this contract keeps track of accumulated premiums in different stages:
|
|
27
|
+
* - activePurePremiums
|
|
28
|
+
* - wonPurePremiums (surplus)
|
|
29
|
+
* - borrowedActivePP (deficit borrowed from activePurePremiums)
|
|
30
|
+
* @custom:security-contact security@ensuro.co
|
|
31
|
+
* @author Ensuro
|
|
32
|
+
*/
|
|
33
|
+
// #invariant_disabled {:msg "Borrow up to activePurePremiums"} _borrowedActivePP <= _activePurePremiums;
|
|
34
|
+
// #invariant_disabled {:msg "Can't borrow if not exhausted before won"} (_borrowedActivePP > 0) ==> _wonPurePremiums == 0;
|
|
35
|
+
contract PolicyPool is IPolicyPool, PausableUpgradeable, UUPSUpgradeable {
|
|
36
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
|
37
|
+
using WadRayMath for uint256;
|
|
38
|
+
using Policy for Policy.PolicyData;
|
|
39
|
+
using DataTypes for DataTypes.ETokenToWadMap;
|
|
40
|
+
using DataTypes for DataTypes.ETokenStatusMap;
|
|
41
|
+
using SafeERC20 for IERC20Metadata;
|
|
42
|
+
|
|
43
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
44
|
+
// solhint-disable-next-line var-name-mixedcase
|
|
45
|
+
uint256 public immutable NEGLIGIBLE_AMOUNT; // init as 10**(decimals-3) == 0.001 USD
|
|
46
|
+
|
|
47
|
+
bytes32 public constant REBALANCE_ROLE = keccak256("REBALANCE_ROLE");
|
|
48
|
+
bytes32 public constant WITHDRAW_WON_PREMIUMS_ROLE = keccak256("WITHDRAW_WON_PREMIUMS_ROLE");
|
|
49
|
+
|
|
50
|
+
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
|
|
51
|
+
bytes32 public constant LEVEL1_ROLE = keccak256("LEVEL1_ROLE");
|
|
52
|
+
bytes32 public constant LEVEL2_ROLE = keccak256("LEVEL2_ROLE");
|
|
53
|
+
bytes32 public constant LEVEL3_ROLE = keccak256("LEVEL3_ROLE");
|
|
54
|
+
|
|
55
|
+
uint256 public constant MAX_ETOKENS = 10;
|
|
56
|
+
|
|
57
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
58
|
+
IPolicyPoolConfig internal immutable _config;
|
|
59
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
60
|
+
IERC20Metadata internal immutable _currency;
|
|
61
|
+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
62
|
+
IPolicyNFT internal immutable _policyNFT;
|
|
63
|
+
|
|
64
|
+
DataTypes.ETokenStatusMap internal _eTokens;
|
|
65
|
+
|
|
66
|
+
mapping(uint256 => bytes32) internal _policies;
|
|
67
|
+
mapping(uint256 => DataTypes.ETokenToWadMap) internal _policiesFunds;
|
|
68
|
+
|
|
69
|
+
uint256 internal _activePurePremiums; // sum of pure-premiums of active policies - In Wad
|
|
70
|
+
uint256 internal _borrowedActivePP; // amount borrowed from active pure premiums to pay defaulted policies
|
|
71
|
+
uint256 internal _wonPurePremiums; // amount of pure premiums won from non-defaulted policies
|
|
72
|
+
|
|
73
|
+
event NewPolicy(IRiskModule indexed riskModule, Policy.PolicyData policy);
|
|
74
|
+
event PolicyRebalanced(IRiskModule indexed riskModule, uint256 indexed policyId);
|
|
75
|
+
event PolicyResolved(IRiskModule indexed riskModule, uint256 indexed policyId, uint256 payout);
|
|
76
|
+
|
|
77
|
+
event ETokenStatusChanged(IEToken indexed eToken, DataTypes.ETokenStatus newStatus);
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Premiums can come in (for free, without liability) with receiveGrant.
|
|
81
|
+
* And can come out (withdrawed to treasury) with withdrawWonPremiums
|
|
82
|
+
*/
|
|
83
|
+
event WonPremiumsInOut(bool moneyIn, uint256 value);
|
|
84
|
+
|
|
85
|
+
modifier onlyAssetManager() {
|
|
86
|
+
require(
|
|
87
|
+
msg.sender == address(_config.assetManager()),
|
|
88
|
+
"Only assetManager can call this function"
|
|
89
|
+
);
|
|
90
|
+
_;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
modifier onlyRole(bytes32 role) {
|
|
94
|
+
_config.checkRole(role, msg.sender);
|
|
95
|
+
_;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
modifier onlyRole2(bytes32 role1, bytes32 role2) {
|
|
99
|
+
_config.checkRole2(role1, role2, msg.sender);
|
|
100
|
+
_;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
104
|
+
constructor(
|
|
105
|
+
IPolicyPoolConfig config_,
|
|
106
|
+
IPolicyNFT policyNFT_,
|
|
107
|
+
IERC20Metadata currency_
|
|
108
|
+
) {
|
|
109
|
+
_config = config_;
|
|
110
|
+
_policyNFT = policyNFT_;
|
|
111
|
+
_currency = currency_;
|
|
112
|
+
NEGLIGIBLE_AMOUNT = 10**(currency_.decimals() - 3);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function initialize() public initializer {
|
|
116
|
+
__UUPSUpgradeable_init();
|
|
117
|
+
__Pausable_init();
|
|
118
|
+
__PolicyPool_init_unchained();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// solhint-disable-next-line func-name-mixedcase
|
|
122
|
+
function __PolicyPool_init_unchained() internal initializer {
|
|
123
|
+
_config.connect();
|
|
124
|
+
require(
|
|
125
|
+
_config.assetManager() == IAssetManager(address(0)),
|
|
126
|
+
"AssetManager can't be set before PolicyPool initialization"
|
|
127
|
+
);
|
|
128
|
+
_policyNFT.connect();
|
|
129
|
+
/*
|
|
130
|
+
_activePurePremiums = 0;
|
|
131
|
+
_borrowedActivePP = 0;
|
|
132
|
+
_wonPurePremiums = 0;
|
|
133
|
+
*/
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// solhint-disable-next-line no-empty-blocks
|
|
137
|
+
function _authorizeUpgrade(address) internal override onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {}
|
|
138
|
+
|
|
139
|
+
function pause() public onlyRole(GUARDIAN_ROLE) {
|
|
140
|
+
_pause();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function unpause() public onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE) {
|
|
144
|
+
_unpause();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function config() external view virtual override returns (IPolicyPoolConfig) {
|
|
148
|
+
return _config;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function currency() external view virtual override returns (IERC20Metadata) {
|
|
152
|
+
return _currency;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function policyNFT() external view virtual override returns (address) {
|
|
156
|
+
return address(_policyNFT);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function purePremiums() public view returns (uint256) {
|
|
160
|
+
return _activePurePremiums + _wonPurePremiums - _borrowedActivePP;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function activePurePremiums() external view returns (uint256) {
|
|
164
|
+
return _activePurePremiums;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function wonPurePremiums() external view returns (uint256) {
|
|
168
|
+
return _wonPurePremiums;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function borrowedActivePP() external view returns (uint256) {
|
|
172
|
+
return _borrowedActivePP;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// #if_succeeds_disabled {:msg "eToken added as active"} _eTokens.get(eToken) == DataTypes.ETokenStatus.active;
|
|
176
|
+
function addEToken(IEToken eToken) external onlyRole(LEVEL1_ROLE) {
|
|
177
|
+
require(_eTokens.length() < MAX_ETOKENS, "Maximum number of ETokens reached");
|
|
178
|
+
require(!_eTokens.contains(eToken), "eToken already in the pool");
|
|
179
|
+
require(address(eToken) != address(0), "eToken can't be zero");
|
|
180
|
+
require(
|
|
181
|
+
IPolicyPoolComponent(address(eToken)).policyPool() == this,
|
|
182
|
+
"EToken not linked to this pool"
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
_eTokens.set(eToken, DataTypes.ETokenStatus.active);
|
|
186
|
+
emit ETokenStatusChanged(eToken, DataTypes.ETokenStatus.active);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function removeEToken(IEToken eToken) external onlyRole(LEVEL3_ROLE) {
|
|
190
|
+
require(_eTokens.get(eToken) == DataTypes.ETokenStatus.deprecated, "EToken not deprecated");
|
|
191
|
+
require(eToken.totalSupply() == 0, "EToken has liquidity, can't be removed");
|
|
192
|
+
emit ETokenStatusChanged(eToken, DataTypes.ETokenStatus.inactive);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function changeETokenStatus(IEToken eToken, DataTypes.ETokenStatus newStatus)
|
|
196
|
+
external
|
|
197
|
+
onlyRole2(GUARDIAN_ROLE, LEVEL1_ROLE)
|
|
198
|
+
{
|
|
199
|
+
require(_eTokens.contains(eToken), "Risk Module not found");
|
|
200
|
+
require(
|
|
201
|
+
newStatus != DataTypes.ETokenStatus.suspended || _config.hasRole(GUARDIAN_ROLE, msg.sender),
|
|
202
|
+
"Only GUARDIAN can suspend eTokens"
|
|
203
|
+
);
|
|
204
|
+
_eTokens.set(eToken, newStatus);
|
|
205
|
+
emit ETokenStatusChanged(eToken, newStatus);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getETokenStatus(IEToken eToken) external view returns (DataTypes.ETokenStatus) {
|
|
209
|
+
return _eTokens.get(eToken);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function setAssetManager(IAssetManager newAssetManager) external override {
|
|
213
|
+
require(msg.sender == address(_config), "Only the PolicyPoolConfig can change assetManager");
|
|
214
|
+
if (address(_config.assetManager()) != address(0)) {
|
|
215
|
+
_config.assetManager().deinvestAll(); // deInvest all assets
|
|
216
|
+
_currency.approve(address(_config.assetManager()), 0); // revoke currency management approval
|
|
217
|
+
}
|
|
218
|
+
if (address(newAssetManager) != address(0)) {
|
|
219
|
+
_currency.approve(address(newAssetManager), type(uint256).max);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// #if_succeeds
|
|
224
|
+
/// {:msg "must take balance from sender"}
|
|
225
|
+
/// _currency.balanceOf(msg.sender) == old(_currency.balanceOf(msg.sender) - amount);
|
|
226
|
+
function deposit(IEToken eToken, uint256 amount) external override whenNotPaused {
|
|
227
|
+
(bool found, DataTypes.ETokenStatus etkStatus) = _eTokens.tryGet(eToken);
|
|
228
|
+
require(found && etkStatus == DataTypes.ETokenStatus.active, "eToken is not active");
|
|
229
|
+
_currency.safeTransferFrom(msg.sender, address(this), amount);
|
|
230
|
+
eToken.deposit(msg.sender, amount);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function withdraw(IEToken eToken, uint256 amount)
|
|
234
|
+
external
|
|
235
|
+
override
|
|
236
|
+
whenNotPaused
|
|
237
|
+
returns (uint256)
|
|
238
|
+
{
|
|
239
|
+
(bool found, DataTypes.ETokenStatus etkStatus) = _eTokens.tryGet(eToken);
|
|
240
|
+
require(
|
|
241
|
+
found &&
|
|
242
|
+
(
|
|
243
|
+
(etkStatus == DataTypes.ETokenStatus.active ||
|
|
244
|
+
etkStatus == DataTypes.ETokenStatus.deprecated)
|
|
245
|
+
),
|
|
246
|
+
"eToken not found or withdraws not allowed"
|
|
247
|
+
);
|
|
248
|
+
address provider = msg.sender;
|
|
249
|
+
uint256 withdrawed = eToken.withdraw(provider, amount);
|
|
250
|
+
if (withdrawed > 0) _transferTo(provider, withdrawed);
|
|
251
|
+
return withdrawed;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function newPolicy(
|
|
255
|
+
Policy.PolicyData memory policy,
|
|
256
|
+
address customer,
|
|
257
|
+
uint96 internalId
|
|
258
|
+
) external override whenNotPaused returns (uint256) {
|
|
259
|
+
IRiskModule rm = policy.riskModule;
|
|
260
|
+
require(address(rm) == msg.sender, "Only the RM can create new policies");
|
|
261
|
+
_config.checkAcceptsNewPolicy(rm);
|
|
262
|
+
policy.id = (uint256(uint160(address(rm))) << 96) + internalId;
|
|
263
|
+
_policies[policy.id] = policy.hash();
|
|
264
|
+
_activePurePremiums += policy.purePremium;
|
|
265
|
+
_lockScr(policy);
|
|
266
|
+
_policyNFT.safeMint(customer, policy.id);
|
|
267
|
+
_currency.safeTransferFrom(customer, address(this), policy.purePremium + policy.premiumForLps);
|
|
268
|
+
_currency.safeTransferFrom(customer, _config.treasury(), policy.premiumForEnsuro);
|
|
269
|
+
if (policy.premiumForRm > 0 && customer != rm.wallet())
|
|
270
|
+
_currency.safeTransferFrom(customer, rm.wallet(), policy.premiumForRm);
|
|
271
|
+
emit NewPolicy(rm, policy);
|
|
272
|
+
return policy.id;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function _lockScr(Policy.PolicyData memory policy) internal {
|
|
276
|
+
uint256 ocean = 0;
|
|
277
|
+
DataTypes.ETokenToWadMap storage policyFunds = _policiesFunds[policy.id];
|
|
278
|
+
|
|
279
|
+
// Initially I iterate over all eTokens and accumulate ocean of eligible ones
|
|
280
|
+
// saves the ocean in policyFunds, later will _distributeScr
|
|
281
|
+
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
282
|
+
(IEToken etk, DataTypes.ETokenStatus etkStatus) = _eTokens.at(i);
|
|
283
|
+
if (etkStatus != DataTypes.ETokenStatus.active) continue;
|
|
284
|
+
if (!etk.accepts(address(policy.riskModule), policy.expiration)) continue;
|
|
285
|
+
uint256 etkOcean = etk.oceanForNewScr();
|
|
286
|
+
if (etkOcean == 0) continue;
|
|
287
|
+
ocean += etkOcean;
|
|
288
|
+
policyFunds.set(etk, etkOcean);
|
|
289
|
+
}
|
|
290
|
+
_distributeScr(policy.scr, policy.interestRate(), ocean, policyFunds);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @dev Distributes SCR amount in policyFunds according to ocean per token
|
|
295
|
+
* @param scr SCR to distribute
|
|
296
|
+
* @param ocean Total ocean available in the ETokens for this SCR
|
|
297
|
+
* @param policyFunds Input: loaded with ocean available for this SCR (sum=ocean)
|
|
298
|
+
Ouput: loaded with locked SRC (sum=scr)
|
|
299
|
+
*/
|
|
300
|
+
function _distributeScr(
|
|
301
|
+
uint256 scr,
|
|
302
|
+
uint256 interestRate,
|
|
303
|
+
uint256 ocean,
|
|
304
|
+
DataTypes.ETokenToWadMap storage policyFunds
|
|
305
|
+
) internal {
|
|
306
|
+
require(ocean >= scr, "Not enought ocean to cover the policy");
|
|
307
|
+
uint256 scrNotLocked = scr;
|
|
308
|
+
|
|
309
|
+
for (uint256 i = 0; i < policyFunds.length(); i++) {
|
|
310
|
+
uint256 etkScr;
|
|
311
|
+
(IEToken etk, uint256 etkOcean) = policyFunds.at(i);
|
|
312
|
+
if (i < policyFunds.length() - 1) etkScr = scr.wadMul(etkOcean).wadDiv(ocean);
|
|
313
|
+
else etkScr = scrNotLocked;
|
|
314
|
+
etk.lockScr(interestRate, etkScr);
|
|
315
|
+
policyFunds.set(etk, etkScr);
|
|
316
|
+
scrNotLocked -= etkScr;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function _balance() internal view returns (uint256) {
|
|
321
|
+
return _currency.balanceOf(address(this));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function _transferTo(address destination, uint256 amount) internal {
|
|
325
|
+
if (amount == 0) return;
|
|
326
|
+
if (_config.assetManager() != IAssetManager(address(0)) && _balance() < amount) {
|
|
327
|
+
_config.assetManager().refillWallet(amount);
|
|
328
|
+
}
|
|
329
|
+
// Calculate again the balance and check if enought, if not call unsolvency_hook
|
|
330
|
+
if (_config.insolvencyHook() != IInsolvencyHook(address(0)) && _balance() < amount) {
|
|
331
|
+
_config.insolvencyHook().outOfCash(amount - _balance());
|
|
332
|
+
}
|
|
333
|
+
_currency.safeTransfer(destination, amount);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function _payFromPool(uint256 toPay) internal returns (uint256) {
|
|
337
|
+
// 1. take from won_pure_premiums
|
|
338
|
+
if (toPay <= _wonPurePremiums) {
|
|
339
|
+
_wonPurePremiums -= toPay;
|
|
340
|
+
return 0;
|
|
341
|
+
}
|
|
342
|
+
if (_wonPurePremiums > 0) {
|
|
343
|
+
toPay -= _wonPurePremiums;
|
|
344
|
+
_wonPurePremiums = 0;
|
|
345
|
+
}
|
|
346
|
+
// 2. borrow from active pure premiums
|
|
347
|
+
if (_activePurePremiums > _borrowedActivePP) {
|
|
348
|
+
if (toPay <= (_activePurePremiums - _borrowedActivePP)) {
|
|
349
|
+
_borrowedActivePP += toPay;
|
|
350
|
+
return 0;
|
|
351
|
+
} else {
|
|
352
|
+
toPay -= _activePurePremiums - _borrowedActivePP;
|
|
353
|
+
_borrowedActivePP = _activePurePremiums;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return toPay;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _storePurePremiumWon(uint256 purePremiumWon) internal {
|
|
360
|
+
if (purePremiumWon == 0) return;
|
|
361
|
+
if (_borrowedActivePP >= purePremiumWon) {
|
|
362
|
+
_borrowedActivePP -= purePremiumWon;
|
|
363
|
+
} else {
|
|
364
|
+
if (_borrowedActivePP > 0) {
|
|
365
|
+
purePremiumWon -= _borrowedActivePP;
|
|
366
|
+
_borrowedActivePP = 0;
|
|
367
|
+
}
|
|
368
|
+
_wonPurePremiums += purePremiumWon;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function _processResolution(
|
|
373
|
+
Policy.PolicyData memory policy,
|
|
374
|
+
bool customerWon,
|
|
375
|
+
uint256 payout
|
|
376
|
+
) internal returns (uint256, uint256) {
|
|
377
|
+
uint256 borrowFromScr; // = 0
|
|
378
|
+
uint256 purePremiumWon; // = 0
|
|
379
|
+
uint256 aux;
|
|
380
|
+
|
|
381
|
+
if (customerWon) {
|
|
382
|
+
_transferTo(_policyNFT.ownerOf(policy.id), payout);
|
|
383
|
+
if (policy.purePremium > payout) {
|
|
384
|
+
purePremiumWon = policy.purePremium - payout;
|
|
385
|
+
} else {
|
|
386
|
+
borrowFromScr = _payFromPool(payout - policy.purePremium);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
// Pay RM and Ensuro
|
|
390
|
+
purePremiumWon = policy.purePremium;
|
|
391
|
+
// cover first _borrowedActivePP
|
|
392
|
+
if (_borrowedActivePP > _activePurePremiums) {
|
|
393
|
+
aux = Math.min(_borrowedActivePP - _activePurePremiums, purePremiumWon);
|
|
394
|
+
_borrowedActivePP -= aux;
|
|
395
|
+
purePremiumWon -= aux;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return (borrowFromScr, purePremiumWon);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function _validatePolicy(Policy.PolicyData memory policy) internal view {
|
|
402
|
+
require(policy.id != 0 && policy.hash() == _policies[policy.id], "Policy not found");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function expirePolicy(Policy.PolicyData calldata policy) external whenNotPaused {
|
|
406
|
+
require(policy.expiration <= block.timestamp, "Policy not expired yet");
|
|
407
|
+
return _resolvePolicy(policy, 0, true);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function resolvePolicy(Policy.PolicyData calldata policy, uint256 payout)
|
|
411
|
+
external
|
|
412
|
+
override
|
|
413
|
+
whenNotPaused
|
|
414
|
+
{
|
|
415
|
+
return _resolvePolicy(policy, payout, false);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function resolvePolicyFullPayout(Policy.PolicyData calldata policy, bool customerWon)
|
|
419
|
+
external
|
|
420
|
+
override
|
|
421
|
+
whenNotPaused
|
|
422
|
+
{
|
|
423
|
+
return _resolvePolicy(policy, customerWon ? policy.payout : 0, false);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function _resolvePolicy(
|
|
427
|
+
Policy.PolicyData memory policy,
|
|
428
|
+
uint256 payout,
|
|
429
|
+
bool expired
|
|
430
|
+
) internal {
|
|
431
|
+
_validatePolicy(policy);
|
|
432
|
+
IRiskModule rm = policy.riskModule;
|
|
433
|
+
require(expired || address(rm) == msg.sender, "Only the RM can resolve policies");
|
|
434
|
+
require(payout == 0 || policy.expiration > block.timestamp, "Can't pay expired policy");
|
|
435
|
+
_config.checkAcceptsResolvePolicy(rm);
|
|
436
|
+
require(payout <= policy.payout, "payout > policy.payout");
|
|
437
|
+
|
|
438
|
+
bool customerWon = payout > 0;
|
|
439
|
+
|
|
440
|
+
_activePurePremiums -= policy.purePremium;
|
|
441
|
+
|
|
442
|
+
(uint256 borrowFromScr, uint256 purePremiumWon) = _processResolution(
|
|
443
|
+
policy,
|
|
444
|
+
customerWon,
|
|
445
|
+
payout
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
if (customerWon) {
|
|
449
|
+
uint256 borrowFromScrLeft;
|
|
450
|
+
borrowFromScrLeft = _updatePolicyFundsCustWon(policy, borrowFromScr);
|
|
451
|
+
if (borrowFromScrLeft > NEGLIGIBLE_AMOUNT)
|
|
452
|
+
borrowFromScrLeft = _takeLoanFromAnyEtk(borrowFromScrLeft);
|
|
453
|
+
require(
|
|
454
|
+
borrowFromScrLeft <= NEGLIGIBLE_AMOUNT,
|
|
455
|
+
"Don't know where to take the rest of the money"
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
purePremiumWon = _updatePolicyFundsCustLost(policy, purePremiumWon);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
_storePurePremiumWon(purePremiumWon); // it's possible in some cases purePremiumWon > 0 && customerWon
|
|
462
|
+
rm.releaseScr(policy.scr);
|
|
463
|
+
|
|
464
|
+
emit PolicyResolved(policy.riskModule, policy.id, payout);
|
|
465
|
+
delete _policies[policy.id];
|
|
466
|
+
delete _policiesFunds[policy.id];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function _interestAdjustment(Policy.PolicyData memory policy)
|
|
470
|
+
internal
|
|
471
|
+
view
|
|
472
|
+
returns (bool, uint256)
|
|
473
|
+
{
|
|
474
|
+
// Calculate interest accrual adjustment
|
|
475
|
+
uint256 aux = policy.accruedInterest();
|
|
476
|
+
if (policy.premiumForLps >= aux) return (true, policy.premiumForLps - aux);
|
|
477
|
+
else return (false, aux - policy.premiumForLps);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function _updatePolicyFundsCustWon(Policy.PolicyData memory policy, uint256 borrowFromScr)
|
|
481
|
+
internal
|
|
482
|
+
returns (uint256)
|
|
483
|
+
{
|
|
484
|
+
uint256 borrowFromScrLeft = 0;
|
|
485
|
+
uint256 interestRate = policy.interestRate();
|
|
486
|
+
(bool positive, uint256 adjustment) = _interestAdjustment(policy);
|
|
487
|
+
|
|
488
|
+
// Iterate policyFunds - unlockScr / adjust / take loan
|
|
489
|
+
DataTypes.ETokenToWadMap storage policyFunds = _policiesFunds[policy.id];
|
|
490
|
+
for (uint256 i = 0; i < policyFunds.length(); i++) {
|
|
491
|
+
(IEToken etk, uint256 etkScr) = policyFunds.at(i);
|
|
492
|
+
etk.unlockScr(interestRate, etkScr);
|
|
493
|
+
etkScr = etkScr.wadDiv(policy.scr);
|
|
494
|
+
// etkScr now represents the share of SCR that's covered by this etk (variable reuse)
|
|
495
|
+
etk.discreteEarning(adjustment.wadMul(etkScr), positive);
|
|
496
|
+
if (borrowFromScr > 0) {
|
|
497
|
+
uint256 aux;
|
|
498
|
+
aux = borrowFromScr.wadMul(etkScr);
|
|
499
|
+
borrowFromScrLeft += aux - etk.lendToPool(aux, true);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return borrowFromScrLeft;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Almost duplicated code from _updatePolicyFundsCustWon but separated to avoid stack depth error
|
|
506
|
+
function _updatePolicyFundsCustLost(Policy.PolicyData memory policy, uint256 purePremiumWon)
|
|
507
|
+
internal
|
|
508
|
+
returns (uint256)
|
|
509
|
+
{
|
|
510
|
+
uint256 interestRate = policy.interestRate();
|
|
511
|
+
(bool positive, uint256 adjustment) = _interestAdjustment(policy);
|
|
512
|
+
|
|
513
|
+
// Iterate policyFunds - unlockScr / adjust / repay loan
|
|
514
|
+
DataTypes.ETokenToWadMap storage policyFunds = _policiesFunds[policy.id];
|
|
515
|
+
for (uint256 i = 0; i < policyFunds.length(); i++) {
|
|
516
|
+
(IEToken etk, uint256 etkScr) = policyFunds.at(i);
|
|
517
|
+
etk.unlockScr(interestRate, etkScr);
|
|
518
|
+
etkScr = etkScr.wadDiv(policy.scr);
|
|
519
|
+
// etkScr now represents the share of SCR that's covered by this etk (variable reuse)
|
|
520
|
+
etk.discreteEarning(adjustment.wadMul(etkScr), positive);
|
|
521
|
+
if (purePremiumWon > 0 && etk.getPoolLoan() > 0) {
|
|
522
|
+
uint256 aux;
|
|
523
|
+
// if debt with token, repay from purePremium
|
|
524
|
+
aux = policy.purePremium.wadMul(etkScr);
|
|
525
|
+
aux = Math.min(purePremiumWon, Math.min(etk.getPoolLoan(), aux));
|
|
526
|
+
etk.repayPoolLoan(aux);
|
|
527
|
+
purePremiumWon -= aux;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return purePremiumWon;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/*
|
|
534
|
+
* Called when the payout to be taken from policyFunds wasn't enought.
|
|
535
|
+
* Then I take loan from the others tokens
|
|
536
|
+
*/
|
|
537
|
+
function _takeLoanFromAnyEtk(uint256 loanLeft) internal returns (uint256) {
|
|
538
|
+
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
539
|
+
(IEToken etk, DataTypes.ETokenStatus etkStatus) = _eTokens.at(i);
|
|
540
|
+
if (etkStatus != DataTypes.ETokenStatus.active) continue;
|
|
541
|
+
loanLeft -= etk.lendToPool(loanLeft, false);
|
|
542
|
+
if (loanLeft <= NEGLIGIBLE_AMOUNT) break;
|
|
543
|
+
}
|
|
544
|
+
return loanLeft;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
*
|
|
549
|
+
* Repays a loan taken with the eToken with the money in the premium pool.
|
|
550
|
+
* The repayment should happen without calling this method when customer losses and eToken is one of the
|
|
551
|
+
* policyFunds. But sometimes we need to take loans from tokens not linked to the policy.
|
|
552
|
+
*
|
|
553
|
+
* returns The amount repaid
|
|
554
|
+
*
|
|
555
|
+
* Requirements:
|
|
556
|
+
*
|
|
557
|
+
* - `eToken` must be `active` or `deprecated`
|
|
558
|
+
*/
|
|
559
|
+
function repayETokenLoan(IEToken eToken) external whenNotPaused returns (uint256) {
|
|
560
|
+
(bool found, DataTypes.ETokenStatus etkStatus) = _eTokens.tryGet(eToken);
|
|
561
|
+
require(
|
|
562
|
+
found &&
|
|
563
|
+
(etkStatus == DataTypes.ETokenStatus.active ||
|
|
564
|
+
etkStatus == DataTypes.ETokenStatus.deprecated),
|
|
565
|
+
"eToken is not active"
|
|
566
|
+
);
|
|
567
|
+
uint256 poolLoan = eToken.getPoolLoan();
|
|
568
|
+
uint256 toPayLater = _payFromPool(poolLoan);
|
|
569
|
+
eToken.repayPoolLoan(poolLoan - toPayLater);
|
|
570
|
+
return poolLoan - toPayLater;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
*
|
|
575
|
+
* Endpoint to receive "free money" and inject that money into the premium pool.
|
|
576
|
+
*
|
|
577
|
+
* Can be used for example if the PolicyPool subscribes an excess loss policy with other company.
|
|
578
|
+
*
|
|
579
|
+
*/
|
|
580
|
+
function receiveGrant(uint256 amount) external override {
|
|
581
|
+
_currency.safeTransferFrom(msg.sender, address(this), amount);
|
|
582
|
+
_storePurePremiumWon(amount);
|
|
583
|
+
emit WonPremiumsInOut(true, amount);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
*
|
|
588
|
+
* Withdraws excess premiums to PolicyPool's treasury.
|
|
589
|
+
* This might be needed in some cases for example if we are deprecating the protocol or the excess premiums
|
|
590
|
+
* are needed to compensate something. Shouldn't be used. Can be disabled revoking role WITHDRAW_WON_PREMIUMS_ROLE
|
|
591
|
+
*
|
|
592
|
+
* returns The amount withdrawed
|
|
593
|
+
*
|
|
594
|
+
* Requirements:
|
|
595
|
+
*
|
|
596
|
+
* - onlyRole(WITHDRAW_WON_PREMIUMS_ROLE)
|
|
597
|
+
* - _wonPurePremiums > 0
|
|
598
|
+
*/
|
|
599
|
+
function withdrawWonPremiums(uint256 amount)
|
|
600
|
+
external
|
|
601
|
+
onlyRole(WITHDRAW_WON_PREMIUMS_ROLE)
|
|
602
|
+
returns (uint256)
|
|
603
|
+
{
|
|
604
|
+
if (amount > _wonPurePremiums) amount = _wonPurePremiums;
|
|
605
|
+
require(amount > 0, "No premiums to withdraw");
|
|
606
|
+
_wonPurePremiums -= amount;
|
|
607
|
+
_transferTo(_config.treasury(), amount);
|
|
608
|
+
emit WonPremiumsInOut(false, amount);
|
|
609
|
+
return amount;
|
|
610
|
+
}
|
|
611
|
+
|
|
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
|
+
function getInvestable() external view override returns (uint256) {
|
|
647
|
+
uint256 borrowedFromEtk = 0;
|
|
648
|
+
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
649
|
+
(
|
|
650
|
+
IEToken etk, /* DataTypes.ETokenStatus etkStatus */
|
|
651
|
+
|
|
652
|
+
) = _eTokens.at(i);
|
|
653
|
+
// TODO: define if not active are investable or not
|
|
654
|
+
borrowedFromEtk += etk.getPoolLoan();
|
|
655
|
+
}
|
|
656
|
+
uint256 premiums = purePremiums();
|
|
657
|
+
if (premiums > borrowedFromEtk) return premiums - borrowedFromEtk;
|
|
658
|
+
else return 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function totalETokenSupply() public view override returns (uint256) {
|
|
662
|
+
uint256 ret = 0;
|
|
663
|
+
for (uint256 i = 0; i < _eTokens.length(); i++) {
|
|
664
|
+
(
|
|
665
|
+
IEToken etk, /* DataTypes.ETokenStatus etkStatus */
|
|
666
|
+
|
|
667
|
+
) = _eTokens.at(i);
|
|
668
|
+
// TODO: define if not active are investable or not
|
|
669
|
+
ret += etk.totalSupply();
|
|
670
|
+
}
|
|
671
|
+
return ret;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function assetEarnings(uint256 amount, bool positive)
|
|
675
|
+
external
|
|
676
|
+
override
|
|
677
|
+
onlyAssetManager
|
|
678
|
+
whenNotPaused
|
|
679
|
+
{
|
|
680
|
+
if (positive) {
|
|
681
|
+
// earnings
|
|
682
|
+
_storePurePremiumWon(amount);
|
|
683
|
+
} else {
|
|
684
|
+
// losses
|
|
685
|
+
_payFromPool(amount); // return value should be 0 if not, losses are more than capital available
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function getPolicyFundCount(uint256 policyId) external view returns (uint256) {
|
|
690
|
+
return _policiesFunds[policyId].length();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function getPolicyFundAt(uint256 policyId, uint256 index)
|
|
694
|
+
external
|
|
695
|
+
view
|
|
696
|
+
returns (IEToken, uint256)
|
|
697
|
+
{
|
|
698
|
+
return _policiesFunds[policyId].at(index);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function getPolicyFund(uint256 policyId, IEToken etoken) external view returns (uint256) {
|
|
702
|
+
(bool success, uint256 amount) = _policiesFunds[policyId].tryGet(etoken);
|
|
703
|
+
if (success) return amount;
|
|
704
|
+
else return 0;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function getETokenCount() external view override returns (uint256) {
|
|
708
|
+
return _eTokens.length();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function getETokenAt(uint256 index) external view override returns (IEToken) {
|
|
712
|
+
(IEToken etk, DataTypes.ETokenStatus etkStatus) = _eTokens.at(index);
|
|
713
|
+
if (etkStatus != DataTypes.ETokenStatus.inactive) return etk;
|
|
714
|
+
else return IEToken(address(0));
|
|
715
|
+
}
|
|
716
|
+
}
|