@ensuro/core 2.0.0-beta8 → 2.0.0-beta9
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 +17 -14
- package/build/contracts/AccessManager.sol/AccessManager.json +2 -2
- package/build/contracts/ERC4626AssetManager.sol/ERC4626AssetManager.json +9 -3
- package/build/contracts/EToken.sol/EToken.json +2 -15
- package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +2 -2
- package/build/contracts/LiquidityThresholdAssetManager.sol/LiquidityThresholdAssetManager.json +7 -1
- package/build/contracts/Policy.sol/Policy.json +2 -2
- package/build/contracts/PolicyPool.sol/PolicyPool.json +21 -2
- package/build/contracts/PremiumsAccount.sol/PremiumsAccount.json +2 -15
- package/build/contracts/Reserve.sol/Reserve.json +0 -13
- package/build/contracts/TimeScaled.sol/TimeScaled.json +3 -17
- package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +2 -2
- package/build/contracts/{WadRayMath.sol → dependencies/WadRayMath.sol}/WadRayMath.json +3 -3
- package/build/contracts/interfaces/IAssetManager.sol/IAssetManager.json +7 -1
- package/build/contracts/interfaces/IPolicyPool.sol/IPolicyPool.json +19 -0
- package/build/contracts/mocks/FixedRateVault.sol/FixedRateVault.json +2 -2
- package/build/contracts/mocks/ForwardProxy.sol/ForwardProxy.json +2 -2
- package/build/contracts/mocks/PolicyHolderMock.sol/PolicyHolderMock.json +28 -2
- package/build/contracts/mocks/PolicyPoolComponentMock.sol/PolicyPoolComponentMock.json +2 -2
- package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMock.json +96 -2
- package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMockForward.json +2 -2
- package/build/contracts/mocks/RiskModuleMock.sol/RiskModuleMock.json +92 -2
- package/build/contracts/mocks/TestCurrency.sol/TestCurrency.json +2 -2
- package/build/contracts/mocks/TestNFT.sol/TestNFT.json +2 -2
- package/contracts/AccessManager.sol +1 -1
- package/contracts/ERC4626AssetManager.sol +5 -2
- package/contracts/EToken.sol +43 -44
- package/contracts/LiquidityThresholdAssetManager.sol +11 -10
- package/contracts/Policy.sol +1 -1
- package/contracts/PolicyPool.sol +152 -19
- package/contracts/PolicyPoolComponent.sol +7 -22
- package/contracts/PremiumsAccount.sol +159 -19
- package/contracts/Reserve.sol +112 -17
- package/contracts/RiskModule.sol +8 -8
- package/contracts/TimeScaled.sol +19 -15
- package/contracts/TrustfulRiskModule.sol +3 -3
- package/contracts/dependencies/WadRayMath.sol +126 -0
- package/contracts/interfaces/IAccessManager.sol +68 -13
- package/contracts/interfaces/IAssetManager.sol +63 -1
- package/contracts/interfaces/IEToken.sol +4 -1
- package/contracts/interfaces/ILPWhitelist.sol +17 -0
- package/contracts/interfaces/IPolicyPool.sol +7 -0
- package/contracts/interfaces/IPolicyPoolComponent.sol +3 -0
- package/contracts/interfaces/IRiskModule.sol +67 -4
- package/contracts/mocks/PolicyHolderMock.sol +21 -0
- package/contracts/mocks/PolicyPoolMock.sol +27 -0
- package/contracts/mocks/RiskModuleMock.sol +7 -1
- package/js/test-utils.js +49 -36
- package/package.json +1 -1
- package/contracts/WadRayMath.sol +0 -135
|
@@ -4,10 +4,12 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
5
5
|
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
6
6
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
|
-
import {
|
|
7
|
+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
|
8
|
+
import {WadRayMath} from "./dependencies/WadRayMath.sol";
|
|
8
9
|
import {IPolicyPool} from "./interfaces/IPolicyPool.sol";
|
|
9
10
|
import {IEToken} from "./interfaces/IEToken.sol";
|
|
10
11
|
import {Reserve} from "./Reserve.sol";
|
|
12
|
+
import {IAccessManager} from "./interfaces/IAccessManager.sol";
|
|
11
13
|
import {IPremiumsAccount} from "./interfaces/IPremiumsAccount.sol";
|
|
12
14
|
import {Policy} from "./Policy.sol";
|
|
13
15
|
import {IEToken} from "./interfaces/IEToken.sol";
|
|
@@ -15,7 +17,14 @@ import {IAssetManager} from "./interfaces/IAssetManager.sol";
|
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* @title Ensuro Premiums Account
|
|
18
|
-
* @dev This contract holds the premiums of a set of risk modules
|
|
20
|
+
* @dev This contract holds the pure premiums of a set of risk modules. The pure premiums is the part of the premium
|
|
21
|
+
* that is expected to cover the losses. The contract keeps track of the pure premiums of the active policies
|
|
22
|
+
* (_activePurePremiums) and the surplus or deficit generated by the finalized policies (pure premiums collected -
|
|
23
|
+
* losses).
|
|
24
|
+
*
|
|
25
|
+
* Collaborates with a junior {EToken} and a senior {EToken} that act as lenders when the premiums aren't enought to
|
|
26
|
+
* cover the losses.
|
|
27
|
+
*
|
|
19
28
|
* @custom:security-contact security@ensuro.co
|
|
20
29
|
* @author Ensuro
|
|
21
30
|
*/
|
|
@@ -23,15 +32,35 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
23
32
|
using Policy for Policy.PolicyData;
|
|
24
33
|
using WadRayMath for uint256;
|
|
25
34
|
using SafeERC20 for IERC20Metadata;
|
|
35
|
+
using SafeCast for uint256;
|
|
26
36
|
|
|
27
37
|
bytes32 public constant WITHDRAW_WON_PREMIUMS_ROLE = keccak256("WITHDRAW_WON_PREMIUMS_ROLE");
|
|
28
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @dev The Junior eToken is the first {EToken} to which the PremiumsAccount will go for credit when it runs out of
|
|
41
|
+
* money. Optional (address(0)).
|
|
42
|
+
*/
|
|
29
43
|
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
30
44
|
IEToken internal immutable _juniorEtk;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @dev The Senior eToken is the second {EToken} to which the PremiumsAccount will go for credit, after trying before
|
|
48
|
+
* with the junior eToken, when it runs out of money. Optional (address(0)).
|
|
49
|
+
*/
|
|
31
50
|
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
32
51
|
IEToken internal immutable _seniorEtk;
|
|
33
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @dev The active pure premiums field keeps track of the pure premiums collected by the active policies of risk
|
|
55
|
+
* modules linked with this PremiumsAccount.
|
|
56
|
+
*/
|
|
34
57
|
uint256 internal _activePurePremiums; // sum of pure-premiums of active policies - In Wad
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @dev The surplus field keeps track of the surplus or deficit (when negative) of the actual payouts made by the
|
|
61
|
+
* PremiumsAccount versus the collected pure premiums. On the negative side, it has a limit defined by `_maxDeficit()`,
|
|
62
|
+
* after that limit, internal loans are taken from the eTokens.
|
|
63
|
+
*/
|
|
35
64
|
int256 internal _surplus;
|
|
36
65
|
|
|
37
66
|
struct PackedParams {
|
|
@@ -41,14 +70,22 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
41
70
|
|
|
42
71
|
PackedParams internal _params;
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
* Premiums can come in (for free, without liability) with receiveGrant.
|
|
73
|
+
/**
|
|
74
|
+
* Premiums can come in (for "free", without liability) with receiveGrant.
|
|
46
75
|
* And can come out (withdrawed to treasury) with withdrawWonPremiums
|
|
76
|
+
*
|
|
77
|
+
* @param moneyIn Indicates if money came in or out (false).
|
|
78
|
+
* @param value The amount of money received or given
|
|
47
79
|
*/
|
|
48
80
|
event WonPremiumsInOut(bool moneyIn, uint256 value);
|
|
49
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @dev Constructor of the contract, sets the immutable fields.
|
|
84
|
+
*
|
|
85
|
+
* @param juniorEtk_ Address of the Junior EToken (first loss lender). `address(0)` if not present.
|
|
86
|
+
* @param seniorEtk_ Address of the Senior EToken (2nd loss lender). `address(0)` if not present.
|
|
87
|
+
*/
|
|
50
88
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
51
|
-
// solhint-disable-next-line no-empty-blocks
|
|
52
89
|
constructor(
|
|
53
90
|
IPolicyPool policyPool_,
|
|
54
91
|
IEToken juniorEtk_,
|
|
@@ -59,14 +96,14 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
59
96
|
}
|
|
60
97
|
|
|
61
98
|
/**
|
|
62
|
-
* @dev
|
|
99
|
+
* @dev Initializes the PremiumsAccount
|
|
63
100
|
*/
|
|
64
101
|
function initialize() public initializer {
|
|
65
102
|
__PremiumsAccount_init();
|
|
66
103
|
}
|
|
67
104
|
|
|
68
105
|
/**
|
|
69
|
-
* @dev Initializes the PremiumsAccount
|
|
106
|
+
* @dev Initializes the PremiumsAccount (to be called by subclasses)
|
|
70
107
|
*/
|
|
71
108
|
// solhint-disable-next-line func-name-mixedcase
|
|
72
109
|
function __PremiumsAccount_init() internal initializer {
|
|
@@ -74,6 +111,10 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
74
111
|
__PremiumsAccount_init_unchained();
|
|
75
112
|
}
|
|
76
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @dev In the initialization, besides settings the parameters, we approve the spending of funds by the eTokens
|
|
116
|
+
* so we don't need to do it on every repayment operation
|
|
117
|
+
*/
|
|
77
118
|
// solhint-disable-next-line func-name-mixedcase
|
|
78
119
|
function __PremiumsAccount_init_unchained() internal initializer {
|
|
79
120
|
/*
|
|
@@ -96,6 +137,14 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
96
137
|
_params.assetManager = newAM;
|
|
97
138
|
}
|
|
98
139
|
|
|
140
|
+
/**
|
|
141
|
+
* @dev This is called by the {Reserve} base class to record the earnings generated by the asset management.
|
|
142
|
+
*
|
|
143
|
+
* @param earningsOrLosses Indicates the amount earned since last time earnings where recorded.
|
|
144
|
+
* - If positive, repays the lons and accumulates the rest in the surplus.
|
|
145
|
+
* - If negative (losses) substracts it from surplus. It never can exceed _maxDeficit and doesn't takes
|
|
146
|
+
* loans to cover asset losses.
|
|
147
|
+
*/
|
|
99
148
|
function _assetEarnings(int256 earningsOrLosses) internal override {
|
|
100
149
|
if (earningsOrLosses > 0) {
|
|
101
150
|
uint256 earnings = uint256(earningsOrLosses);
|
|
@@ -103,11 +152,10 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
103
152
|
if (address(_juniorEtk) != address(0)) earnings = _repayLoan(earnings, _juniorEtk);
|
|
104
153
|
_storePurePremiumWon(earnings);
|
|
105
154
|
} else {
|
|
106
|
-
_payFromPremiums(uint256(-earningsOrLosses));
|
|
155
|
+
require(_payFromPremiums(uint256(-earningsOrLosses)) == 0, "Losses can't exceed maxDeficit");
|
|
107
156
|
}
|
|
108
157
|
}
|
|
109
158
|
|
|
110
|
-
// solhint-disable-next-line no-empty-blocks
|
|
111
159
|
function _validateParameters() internal view override {
|
|
112
160
|
require(
|
|
113
161
|
_params.deficitRatio <= 1e4 && _params.deficitRatio >= 0,
|
|
@@ -119,18 +167,35 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
119
167
|
return uint256(int256(_activePurePremiums) + _surplus);
|
|
120
168
|
}
|
|
121
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @dev Returns the total amount of pure premiums that were collected by the active policies of the risk modules
|
|
172
|
+
* linked to this PremiumsAccount.
|
|
173
|
+
*/
|
|
122
174
|
function activePurePremiums() external view returns (uint256) {
|
|
123
175
|
return _activePurePremiums;
|
|
124
176
|
}
|
|
125
177
|
|
|
178
|
+
/**
|
|
179
|
+
* @dev Returns the surplus between pure premiums collected and payouts of finalized policies. Returns 0 if no surplus
|
|
180
|
+
* or deficit.
|
|
181
|
+
*/
|
|
126
182
|
function wonPurePremiums() external view returns (uint256) {
|
|
127
183
|
return _surplus >= 0 ? uint256(_surplus) : 0;
|
|
128
184
|
}
|
|
129
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @dev Returns the amount of active pure premiums that was used to cover payouts of finalized policies (in excess of
|
|
188
|
+
* collected pure premiums). This is limited by `_maxDeficit()`
|
|
189
|
+
*/
|
|
130
190
|
function borrowedActivePP() external view returns (uint256) {
|
|
131
191
|
return _surplus >= 0 ? 0 : uint256(-_surplus);
|
|
132
192
|
}
|
|
133
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @dev Returns the surplus between pure premiums collected and payouts of finalized policies. Losses where more than
|
|
196
|
+
* premiums collected, returns a negative number that indicates the amount of the active pure premiums that was used
|
|
197
|
+
* to cover finalized premiums.
|
|
198
|
+
*/
|
|
134
199
|
function surplus() external view returns (int256) {
|
|
135
200
|
return _surplus;
|
|
136
201
|
}
|
|
@@ -143,14 +208,38 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
143
208
|
return _juniorEtk;
|
|
144
209
|
}
|
|
145
210
|
|
|
211
|
+
/**
|
|
212
|
+
* @dev Returns the maximum deficit that's supported by the PremiumsAccount. If more money is needed, it must take
|
|
213
|
+
* loans from the eTokens. The value is calculated as a fraction of the active pure premiums. The fraction is
|
|
214
|
+
* regulated by the `deficitRatio` parameter that indicates the percentage of the active pure premiums that can be
|
|
215
|
+
* used to cover payouts of finalized policies. In many cases is fine to use the active pure premiums to cover the
|
|
216
|
+
* losses because in most cases the policies with payout are triggered long time before the policies without payout.
|
|
217
|
+
* But this also can be dangerous because it can be postponing the losses that should impact on liquidity providers.
|
|
218
|
+
*
|
|
219
|
+
* @param ratio The ratio used in the calculation of the deficit. It's the deficitRatio parameter (whether the current
|
|
220
|
+
* one or the new one when it's being modified).
|
|
221
|
+
*/
|
|
146
222
|
function _maxDeficit(uint256 ratio) internal view returns (int256) {
|
|
147
223
|
return -int256(_activePurePremiums.wadMul(ratio));
|
|
148
224
|
}
|
|
149
225
|
|
|
226
|
+
/**
|
|
227
|
+
* @dev Returns the percentage of the active pure premiums that can be used to cover losses of finalized policies.
|
|
228
|
+
*/
|
|
150
229
|
function deficitRatio() public view returns (uint256) {
|
|
151
230
|
return uint256(_params.deficitRatio) * 1e14; // 4 -> 18 decimals
|
|
152
231
|
}
|
|
153
232
|
|
|
233
|
+
/**
|
|
234
|
+
* @dev Changes the `deficitRatio` parameter.
|
|
235
|
+
*
|
|
236
|
+
* Events:
|
|
237
|
+
* - Emits GovernanceAction with action = setDeficitRatio or setDeficitRatioWithAdjustment if an adjustment was made.
|
|
238
|
+
*
|
|
239
|
+
* @param adjustment If true and the new ratio leaves `_surplus < -_maxDeficit()`, if adjusts the _surplus to the new
|
|
240
|
+
* `_maxDeficit()` and borrows the difference from the eTokens.
|
|
241
|
+
* If false and the new ratio leaves `_surplus < -_maxDeficit()`, the operation is reverted.
|
|
242
|
+
*/
|
|
154
243
|
function setDeficitRatio(uint256 newRatio, bool adjustment)
|
|
155
244
|
external
|
|
156
245
|
onlyComponentRole(LEVEL2_ROLE)
|
|
@@ -158,15 +247,27 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
158
247
|
require(newRatio <= 1e18 && newRatio >= 0, "Validation: deficitRatio must be <= 1");
|
|
159
248
|
int256 maxDeficit = _maxDeficit(newRatio);
|
|
160
249
|
require(adjustment || _surplus >= maxDeficit, "Validation: surplus must be >= maxDeficit");
|
|
250
|
+
IAccessManager.GovernanceActions action = IAccessManager.GovernanceActions.setDeficitRatio;
|
|
161
251
|
if (_surplus < maxDeficit) {
|
|
162
252
|
// Do the adjustment
|
|
163
253
|
uint256 borrow = uint256(-_surplus + maxDeficit);
|
|
164
254
|
_surplus = maxDeficit;
|
|
165
255
|
_borrowFromEtk(borrow, address(this), address(_juniorEtk) != address(0));
|
|
256
|
+
action = IAccessManager.GovernanceActions.setDeficitRatioWithAdjustment;
|
|
166
257
|
}
|
|
167
|
-
_params.deficitRatio =
|
|
258
|
+
_params.deficitRatio = (newRatio / 1e14).toUint16();
|
|
259
|
+
_parameterChanged(action, newRatio, false);
|
|
168
260
|
}
|
|
169
261
|
|
|
262
|
+
/**
|
|
263
|
+
* @dev Internal function called when money in the PremiumsAccount is not enought and we need to borrow from the
|
|
264
|
+
* eTokens.
|
|
265
|
+
*
|
|
266
|
+
* @param borrow The amount to borrow.
|
|
267
|
+
* @param receiver The address that will receive the money of the loan. Usually is the policy holder if this is called
|
|
268
|
+
* in the context of a policy payout.
|
|
269
|
+
* @param jrEtk If true it indicates that the loan is asked first from the junior eToken.
|
|
270
|
+
*/
|
|
170
271
|
function _borrowFromEtk(
|
|
171
272
|
uint256 borrow,
|
|
172
273
|
address receiver,
|
|
@@ -186,6 +287,13 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
186
287
|
}
|
|
187
288
|
}
|
|
188
289
|
|
|
290
|
+
/**
|
|
291
|
+
* @dev Updates the `_surplus` field with the payment made. Since the _surplus can never exceed `_maxDeficit()`,
|
|
292
|
+
* returns the remaining amount in case something can't be paid from the PremiumsAccount.
|
|
293
|
+
*
|
|
294
|
+
* @param toPay The amount to pay.
|
|
295
|
+
* @return The amount that couldn't be paid from the premiums account.
|
|
296
|
+
*/
|
|
189
297
|
function _payFromPremiums(uint256 toPay) internal returns (uint256) {
|
|
190
298
|
int256 newSurplus = _surplus - int256(toPay);
|
|
191
299
|
int256 maxDeficit = _maxDeficit(deficitRatio());
|
|
@@ -197,38 +305,54 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
197
305
|
return uint256(-newSurplus + maxDeficit);
|
|
198
306
|
}
|
|
199
307
|
|
|
308
|
+
/**
|
|
309
|
+
* @dev Stores an earned pure premium. Adds to the surplus, increasing the surplus if it was positive or reducing the
|
|
310
|
+
* deficit if it was negative.
|
|
311
|
+
*
|
|
312
|
+
* @param purePremiumWon The amount earned
|
|
313
|
+
*/
|
|
200
314
|
function _storePurePremiumWon(uint256 purePremiumWon) internal {
|
|
201
315
|
if (purePremiumWon == 0) return;
|
|
202
316
|
_surplus += int256(purePremiumWon);
|
|
203
317
|
}
|
|
204
318
|
|
|
205
|
-
// TODO: restore repayETokenLoan?
|
|
206
|
-
|
|
207
319
|
/**
|
|
208
320
|
*
|
|
209
321
|
* Endpoint to receive "free money" and inject that money into the premium pool.
|
|
210
322
|
*
|
|
211
323
|
* Can be used for example if the PolicyPool subscribes an excess loss policy with other company.
|
|
212
324
|
*
|
|
325
|
+
* Requirements:
|
|
326
|
+
* - The sender needs to approve the spending of `currency()` by this contract.
|
|
327
|
+
*
|
|
328
|
+
* Events:
|
|
329
|
+
* - Emits {WonPremiumsInOut} with moneyIn = true
|
|
330
|
+
*
|
|
331
|
+
* @param amount The amount to be transferred.
|
|
213
332
|
*/
|
|
214
333
|
function receiveGrant(uint256 amount) external {
|
|
215
|
-
currency().safeTransferFrom(
|
|
334
|
+
currency().safeTransferFrom(_msgSender(), address(this), amount);
|
|
216
335
|
_storePurePremiumWon(amount);
|
|
217
336
|
emit WonPremiumsInOut(true, amount);
|
|
218
337
|
}
|
|
219
338
|
|
|
220
339
|
/**
|
|
221
340
|
*
|
|
222
|
-
* Withdraws excess premiums to
|
|
341
|
+
* Withdraws excess premiums (surplus) to the destination.
|
|
342
|
+
*
|
|
223
343
|
* This might be needed in some cases for example if we are deprecating the protocol or the excess premiums
|
|
224
344
|
* are needed to compensate something. Shouldn't be used. Can be disabled revoking role WITHDRAW_WON_PREMIUMS_ROLE
|
|
225
345
|
*
|
|
226
|
-
* returns The amount withdrawed
|
|
227
|
-
*
|
|
228
346
|
* Requirements:
|
|
229
|
-
*
|
|
230
347
|
* - onlyGlobalOrComponentRole(WITHDRAW_WON_PREMIUMS_ROLE)
|
|
231
348
|
* - _wonPurePremiums > 0
|
|
349
|
+
*
|
|
350
|
+
* Events:
|
|
351
|
+
* - Emits {WonPremiumsInOut} with moneyIn = false
|
|
352
|
+
*
|
|
353
|
+
* @param amount The amount to withdraw
|
|
354
|
+
* @param destination The address that will receive the transferred funds.
|
|
355
|
+
* @return Returns the actual amount withdrawn.
|
|
232
356
|
*/
|
|
233
357
|
function withdrawWonPremiums(uint256 amount, address destination)
|
|
234
358
|
external
|
|
@@ -283,6 +407,11 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
283
407
|
}
|
|
284
408
|
}
|
|
285
409
|
|
|
410
|
+
/**
|
|
411
|
+
* @dev Internal function that calls the eTokens to lock the solvency capital when the policy is created.
|
|
412
|
+
*
|
|
413
|
+
* @param policy The policy created
|
|
414
|
+
*/
|
|
286
415
|
function _unlockScr(Policy.PolicyData memory policy) internal {
|
|
287
416
|
if (policy.jrScr > 0) {
|
|
288
417
|
_juniorEtk.unlockScr(
|
|
@@ -300,12 +429,23 @@ contract PremiumsAccount is IPremiumsAccount, Reserve {
|
|
|
300
429
|
}
|
|
301
430
|
}
|
|
302
431
|
|
|
432
|
+
/**
|
|
433
|
+
* @dev Internal function that repays a loan taken (if any outstanding) from the an eToken
|
|
434
|
+
*
|
|
435
|
+
* @param purePremiumWon The amount earned and available for loan repayment.
|
|
436
|
+
* @param etk The eToken with the potential debt
|
|
437
|
+
* @return The excess amount of the purePremiumWon that wasn't used for the loan repayment.
|
|
438
|
+
*/
|
|
303
439
|
function _repayLoan(uint256 purePremiumWon, IEToken etk) internal returns (uint256) {
|
|
304
440
|
if (purePremiumWon < NEGLIGIBLE_AMOUNT) return purePremiumWon;
|
|
305
441
|
uint256 borrowedFromEtk = etk.getLoan(address(this));
|
|
306
442
|
if (borrowedFromEtk == 0) return purePremiumWon;
|
|
307
|
-
uint256 repayAmount =
|
|
308
|
-
|
|
443
|
+
uint256 repayAmount = Math.min(purePremiumWon, borrowedFromEtk);
|
|
444
|
+
|
|
445
|
+
// If not enought liquidity, it deinvests from the asset manager
|
|
446
|
+
if (currency().balanceOf(address(this)) < repayAmount) {
|
|
447
|
+
_refillWallet(repayAmount);
|
|
448
|
+
}
|
|
309
449
|
etk.repayLoan(repayAmount, address(this));
|
|
310
450
|
return purePremiumWon - repayAmount;
|
|
311
451
|
}
|
package/contracts/Reserve.sol
CHANGED
|
@@ -11,7 +11,13 @@ import {PolicyPoolComponent} from "./PolicyPoolComponent.sol";
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @title Base contract for Ensuro cash reserves
|
|
14
|
-
* @dev This contract implements the methods related with management of the reserves and payments
|
|
14
|
+
* @dev This contract implements the methods related with management of the reserves and payments. {EToken} and
|
|
15
|
+
* {PremiumsAccount} inherit from this contract.
|
|
16
|
+
*
|
|
17
|
+
* These contracts have an asset manager {IAssetManager} that's a strategy contract that runs in the same context
|
|
18
|
+
* (called with delegatecall) that apply some strategy to reinvest the assets managed by the contract to generate
|
|
19
|
+
* additional returns.
|
|
20
|
+
*
|
|
15
21
|
* @custom:security-contact security@ensuro.co
|
|
16
22
|
* @author Ensuro
|
|
17
23
|
*/
|
|
@@ -21,35 +27,100 @@ abstract contract Reserve is PolicyPoolComponent {
|
|
|
21
27
|
|
|
22
28
|
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
|
23
29
|
// solhint-disable-next-line var-name-mixedcase
|
|
24
|
-
uint256
|
|
30
|
+
uint256 internal immutable NEGLIGIBLE_AMOUNT; // init as 10**(decimals/2) == 0.001 USD
|
|
25
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @dev Reserve constructor. Calculates NEGLIGIBLE_AMOUNT to avoid rounding errors.
|
|
34
|
+
*
|
|
35
|
+
* @param policyPool_ The {PolicyPool} where this reserve will be plugged
|
|
36
|
+
*/
|
|
26
37
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
27
38
|
constructor(IPolicyPool policyPool_) PolicyPoolComponent(policyPool_) {
|
|
28
39
|
NEGLIGIBLE_AMOUNT = 10**(policyPool_.currency().decimals() / 2);
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @dev Refills the reserve's balance, deinvesting from the asset manager to be able to make a payment
|
|
44
|
+
*
|
|
45
|
+
* @param amount The amount of the payment that needs to be made
|
|
46
|
+
* @return Returns the actual amount deinvested (how much the `currency().balanceof(this)` was increased). It might be
|
|
47
|
+
* more than `amount` because the asset manager might want to give more liquidity to the reserve to avoid further
|
|
48
|
+
* deinvestments. After the call, the `currency().balanceof(this)` should be greater than `amount` (unless unsolvency
|
|
49
|
+
* problem).
|
|
50
|
+
*/
|
|
51
|
+
function _refillWallet(uint256 amount) internal returns (uint256) {
|
|
52
|
+
address am = address(assetManager());
|
|
53
|
+
if (am != address(0)) {
|
|
54
|
+
bytes memory result = am.functionDelegateCall(
|
|
55
|
+
abi.encodeWithSelector(IAssetManager.refillWallet.selector, amount),
|
|
56
|
+
"Error refilling wallet"
|
|
57
|
+
);
|
|
58
|
+
return abi.decode(result, (uint256));
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @dev Internal function that transfers money to a destination. It might need to call `_refillWallet` to deinvest
|
|
65
|
+
* some money to have enought liquidity for the payment.
|
|
66
|
+
*
|
|
67
|
+
* @param destination The destination of the transfer.
|
|
68
|
+
* @param amount The amount to be transferred.
|
|
69
|
+
*/
|
|
31
70
|
function _transferTo(address destination, uint256 amount) internal {
|
|
32
71
|
if (amount == 0) return;
|
|
33
72
|
uint256 balance = currency().balanceOf(address(this));
|
|
34
73
|
if (balance < amount) {
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
74
|
+
balance += _refillWallet(amount);
|
|
75
|
+
if (amount > balance) {
|
|
76
|
+
if ((amount - balance) < NEGLIGIBLE_AMOUNT) {
|
|
77
|
+
amount = balance;
|
|
78
|
+
} // else - No need to do anything since safeTransfer will fail anyway
|
|
41
79
|
}
|
|
42
|
-
if ((amount - balance) < NEGLIGIBLE_AMOUNT) amount = balance;
|
|
43
80
|
}
|
|
44
81
|
currency().safeTransfer(destination, amount);
|
|
45
82
|
}
|
|
46
83
|
|
|
84
|
+
/**
|
|
85
|
+
* @dev Returns the address of the asset manager for this reserve. The asset manager is the contract that manages the
|
|
86
|
+
* funds to generate additional yields. Can be `address(0)` if no asset manager has been set.
|
|
87
|
+
*/
|
|
47
88
|
function assetManager() public view virtual returns (IAssetManager);
|
|
48
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @dev Internal function that needs to be implemented by child contracts because they might store the asset manager
|
|
92
|
+
* address in a different way. This function just stores the value, doesn't do any validation (validations are done on
|
|
93
|
+
* `setAssetManager`.
|
|
94
|
+
*
|
|
95
|
+
* @param newAM The address of the new asset manager for the reserve.
|
|
96
|
+
*/
|
|
49
97
|
function _setAssetManager(IAssetManager newAM) internal virtual;
|
|
50
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @dev Internal function that needs to be implemented by child contracts to record the earnings (or losses if
|
|
101
|
+
* negative) generated by the asset management.
|
|
102
|
+
*
|
|
103
|
+
* @param earnings The amount of earnings (or losses if negative) generated since last time the earnings were
|
|
104
|
+
* recorded.
|
|
105
|
+
*/
|
|
51
106
|
function _assetEarnings(int256 earnings) internal virtual;
|
|
52
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @dev Sets the asset manager for this reserve. If the reserve had previously an asset manager, it will deinvest all
|
|
110
|
+
* the funds, making all of the liquid in the reserve balance.
|
|
111
|
+
*
|
|
112
|
+
* Requirements:
|
|
113
|
+
* - The caller must have been granted of global or component roles GUARDIAN_ROLE or LEVEL1_ROLE.
|
|
114
|
+
*
|
|
115
|
+
* Events:
|
|
116
|
+
* - Emits ComponentChanged with action setAssetManager or setAssetManagerForced
|
|
117
|
+
*
|
|
118
|
+
* @param newAM The address of the new asset manager to assign to the reserve. If is `address(0)` it means the reserve
|
|
119
|
+
* will not have an asset manager. If not `address(0)` it MUST be a contract following the IAssetManager interface.
|
|
120
|
+
* @param force When a previous asset manager exists, before setting the new one, the funds are deinvested. When
|
|
121
|
+
* `force` is true, an error in the deinvestAll() operation is ignored. When `force` is false, if `deinvestAll()`
|
|
122
|
+
* fails, it reverts.
|
|
123
|
+
*/
|
|
53
124
|
function setAssetManager(IAssetManager newAM, bool force)
|
|
54
125
|
external
|
|
55
126
|
onlyGlobalOrComponentRole2(GUARDIAN_ROLE, LEVEL1_ROLE)
|
|
@@ -87,32 +158,56 @@ abstract contract Reserve is PolicyPoolComponent {
|
|
|
87
158
|
_componentChanged(action, address(newAM));
|
|
88
159
|
}
|
|
89
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @dev Calls {IAssetManager-rebalance} of the assigned asset manager (fails if no asset manager). This operation is
|
|
163
|
+
* intended to give the opportunity to rebalance the liquid and invested for better returns and/or gas optimization.
|
|
164
|
+
*
|
|
165
|
+
* - Emits {IAssetManager-MoneyInvested} or {IAssetManager-MoneyDeinvested}
|
|
166
|
+
*/
|
|
90
167
|
function rebalance() public whenNotPaused {
|
|
91
|
-
address
|
|
92
|
-
|
|
93
|
-
|
|
168
|
+
address(assetManager()).functionDelegateCall(
|
|
169
|
+
abi.encodeWithSelector(IAssetManager.rebalance.selector)
|
|
170
|
+
);
|
|
94
171
|
}
|
|
95
172
|
|
|
173
|
+
/**
|
|
174
|
+
* @dev Calls {IAssetManager-recordEarnings} of the assigned asset manager (fails if no asset manager). The asset
|
|
175
|
+
* manager will return the earnings since last time the earnings where recorded. It then calls `_assetEarnings` to
|
|
176
|
+
* reflect the earnings in the way defined for each reserve.
|
|
177
|
+
*
|
|
178
|
+
* - Emits {IAssetManager-EarningsRecorded}
|
|
179
|
+
*/
|
|
96
180
|
function recordEarnings() public whenNotPaused {
|
|
97
|
-
|
|
98
|
-
require(am != address(0), "No asset manager");
|
|
99
|
-
bytes memory result = am.functionDelegateCall(
|
|
181
|
+
bytes memory result = address(assetManager()).functionDelegateCall(
|
|
100
182
|
abi.encodeWithSelector(IAssetManager.recordEarnings.selector)
|
|
101
183
|
);
|
|
102
184
|
_assetEarnings(abi.decode(result, (int256)));
|
|
103
185
|
}
|
|
104
186
|
|
|
187
|
+
/**
|
|
188
|
+
* @dev Function that calls both `recordEarnings()` and `rebalance()` (in that order). Usually scheduled to run once a
|
|
189
|
+
* day by a keeper or crontask.
|
|
190
|
+
*/
|
|
105
191
|
function checkpoint() external whenNotPaused {
|
|
106
192
|
recordEarnings();
|
|
107
193
|
rebalance();
|
|
108
194
|
}
|
|
109
195
|
|
|
196
|
+
/**
|
|
197
|
+
* @dev This function allows to call custom functions of the asset manager (for example for setting parameters).
|
|
198
|
+
* This functions will be called with `delegatecall`, in the context of the reserve.
|
|
199
|
+
*
|
|
200
|
+
* Requirements:
|
|
201
|
+
* - The caller must have been granted of global or component roles LEVEL2_ROLE.
|
|
202
|
+
*
|
|
203
|
+
* @param functionCall Abi encoded function call to make.
|
|
204
|
+
* @return Returns the return value of the function called, to be decoded by the receiver.
|
|
205
|
+
*/
|
|
110
206
|
function forwardToAssetManager(bytes memory functionCall)
|
|
111
207
|
external
|
|
112
208
|
onlyGlobalOrComponentRole(LEVEL2_ROLE)
|
|
113
209
|
returns (bytes memory)
|
|
114
210
|
{
|
|
115
|
-
|
|
116
|
-
return am.functionDelegateCall(functionCall);
|
|
211
|
+
return address(assetManager()).functionDelegateCall(functionCall);
|
|
117
212
|
}
|
|
118
213
|
}
|
package/contracts/RiskModule.sol
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
|
5
|
+
import {WadRayMath} from "./dependencies/WadRayMath.sol";
|
|
5
6
|
import {IPolicyPool} from "./interfaces/IPolicyPool.sol";
|
|
6
7
|
import {PolicyPoolComponent} from "./PolicyPoolComponent.sol";
|
|
7
8
|
import {IRiskModule} from "./interfaces/IRiskModule.sol";
|
|
@@ -18,6 +19,7 @@ import {Policy} from "./Policy.sol";
|
|
|
18
19
|
abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
19
20
|
using Policy for Policy.PolicyData;
|
|
20
21
|
using WadRayMath for uint256;
|
|
22
|
+
using SafeCast for uint256;
|
|
21
23
|
|
|
22
24
|
uint256 internal constant SECONDS_IN_YEAR_WAD = 31536000e18; /* 365 * 24 * 3600 * 10e18 */
|
|
23
25
|
|
|
@@ -153,7 +155,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
|
153
155
|
|
|
154
156
|
function _wadTo4(uint256 value) internal pure returns (uint16) {
|
|
155
157
|
// Wad to 4 decimals
|
|
156
|
-
return
|
|
158
|
+
return (value / 1e14).toUint16();
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
// solhint-disable-next-line func-name-mixedcase
|
|
@@ -164,7 +166,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
|
164
166
|
|
|
165
167
|
function _amountToX(uint8 decimals, uint256 value) internal view returns (uint32) {
|
|
166
168
|
// Wad to X decimals
|
|
167
|
-
return
|
|
169
|
+
return (value / 10**(currency().decimals() - decimals)).toUint32();
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
function maxPayoutPerPolicy() public view override returns (uint256) {
|
|
@@ -235,9 +237,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
|
235
237
|
_params.exposureLimit = _amountToX(0, newValue);
|
|
236
238
|
} else if (param == Parameter.maxDuration) {
|
|
237
239
|
require(!tweak, "Tweak exceeded");
|
|
238
|
-
_params.maxDuration =
|
|
239
|
-
} else {
|
|
240
|
-
revert("Invalid param!");
|
|
240
|
+
_params.maxDuration = newValue.toUint16();
|
|
241
241
|
}
|
|
242
242
|
_parameterChanged(
|
|
243
243
|
IAccessManager.GovernanceActions(
|
|
@@ -329,7 +329,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
|
329
329
|
"You must allow ENSURO to transfer the premium"
|
|
330
330
|
);
|
|
331
331
|
require(
|
|
332
|
-
payer ==
|
|
332
|
+
payer == _msgSender() || _policyPool.currency().allowance(payer, _msgSender()) >= premium,
|
|
333
333
|
"Payer must allow caller to transfer the premium"
|
|
334
334
|
);
|
|
335
335
|
require(payout <= maxPayoutPerPolicy(), "RiskModule: Payout is more than maximum per policy");
|
|
@@ -342,7 +342,7 @@ abstract contract RiskModule is IRiskModule, PolicyPoolComponent {
|
|
|
342
342
|
expiration
|
|
343
343
|
);
|
|
344
344
|
_activeExposure += policy.payout;
|
|
345
|
-
require(_activeExposure <= exposureLimit(), "RiskModule:
|
|
345
|
+
require(_activeExposure <= exposureLimit(), "RiskModule: Exposure limit exceeded");
|
|
346
346
|
uint256 policyId = _policyPool.newPolicy(policy, payer, onBehalfOf, internalId);
|
|
347
347
|
policy.id = policyId;
|
|
348
348
|
return policy;
|