@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.
Files changed (50) hide show
  1. package/README.md +17 -14
  2. package/build/contracts/AccessManager.sol/AccessManager.json +2 -2
  3. package/build/contracts/ERC4626AssetManager.sol/ERC4626AssetManager.json +9 -3
  4. package/build/contracts/EToken.sol/EToken.json +2 -15
  5. package/build/contracts/LPManualWhitelist.sol/LPManualWhitelist.json +2 -2
  6. package/build/contracts/LiquidityThresholdAssetManager.sol/LiquidityThresholdAssetManager.json +7 -1
  7. package/build/contracts/Policy.sol/Policy.json +2 -2
  8. package/build/contracts/PolicyPool.sol/PolicyPool.json +21 -2
  9. package/build/contracts/PremiumsAccount.sol/PremiumsAccount.json +2 -15
  10. package/build/contracts/Reserve.sol/Reserve.json +0 -13
  11. package/build/contracts/TimeScaled.sol/TimeScaled.json +3 -17
  12. package/build/contracts/TrustfulRiskModule.sol/TrustfulRiskModule.json +2 -2
  13. package/build/contracts/{WadRayMath.sol → dependencies/WadRayMath.sol}/WadRayMath.json +3 -3
  14. package/build/contracts/interfaces/IAssetManager.sol/IAssetManager.json +7 -1
  15. package/build/contracts/interfaces/IPolicyPool.sol/IPolicyPool.json +19 -0
  16. package/build/contracts/mocks/FixedRateVault.sol/FixedRateVault.json +2 -2
  17. package/build/contracts/mocks/ForwardProxy.sol/ForwardProxy.json +2 -2
  18. package/build/contracts/mocks/PolicyHolderMock.sol/PolicyHolderMock.json +28 -2
  19. package/build/contracts/mocks/PolicyPoolComponentMock.sol/PolicyPoolComponentMock.json +2 -2
  20. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMock.json +96 -2
  21. package/build/contracts/mocks/PolicyPoolMock.sol/PolicyPoolMockForward.json +2 -2
  22. package/build/contracts/mocks/RiskModuleMock.sol/RiskModuleMock.json +92 -2
  23. package/build/contracts/mocks/TestCurrency.sol/TestCurrency.json +2 -2
  24. package/build/contracts/mocks/TestNFT.sol/TestNFT.json +2 -2
  25. package/contracts/AccessManager.sol +1 -1
  26. package/contracts/ERC4626AssetManager.sol +5 -2
  27. package/contracts/EToken.sol +43 -44
  28. package/contracts/LiquidityThresholdAssetManager.sol +11 -10
  29. package/contracts/Policy.sol +1 -1
  30. package/contracts/PolicyPool.sol +152 -19
  31. package/contracts/PolicyPoolComponent.sol +7 -22
  32. package/contracts/PremiumsAccount.sol +159 -19
  33. package/contracts/Reserve.sol +112 -17
  34. package/contracts/RiskModule.sol +8 -8
  35. package/contracts/TimeScaled.sol +19 -15
  36. package/contracts/TrustfulRiskModule.sol +3 -3
  37. package/contracts/dependencies/WadRayMath.sol +126 -0
  38. package/contracts/interfaces/IAccessManager.sol +68 -13
  39. package/contracts/interfaces/IAssetManager.sol +63 -1
  40. package/contracts/interfaces/IEToken.sol +4 -1
  41. package/contracts/interfaces/ILPWhitelist.sol +17 -0
  42. package/contracts/interfaces/IPolicyPool.sol +7 -0
  43. package/contracts/interfaces/IPolicyPoolComponent.sol +3 -0
  44. package/contracts/interfaces/IRiskModule.sol +67 -4
  45. package/contracts/mocks/PolicyHolderMock.sol +21 -0
  46. package/contracts/mocks/PolicyPoolMock.sol +27 -0
  47. package/contracts/mocks/RiskModuleMock.sol +7 -1
  48. package/js/test-utils.js +49 -36
  49. package/package.json +1 -1
  50. 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 {WadRayMath} from "./WadRayMath.sol";
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 Public initialize Initializes the PremiumsAccount
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 = uint16(newRatio / 1e14);
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(msg.sender, address(this), amount);
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 PolicyPool's treasury.
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 = borrowedFromEtk > purePremiumWon ? purePremiumWon : borrowedFromEtk;
308
- // TODO: make sure the balance is available or deinvest
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
  }
@@ -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 public immutable NEGLIGIBLE_AMOUNT; // init as 10**(decimals/2) == 0.001 USD
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
- address am = address(assetManager());
36
- if (am != address(0)) {
37
- am.functionDelegateCall(
38
- abi.encodeWithSelector(IAssetManager.refillWallet.selector, amount),
39
- "Error refilling wallet"
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 am = address(assetManager());
92
- require(am != address(0), "No asset manager");
93
- am.functionDelegateCall(abi.encodeWithSelector(IAssetManager.rebalance.selector));
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
- address am = address(assetManager());
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
- address am = address(assetManager());
116
- return am.functionDelegateCall(functionCall);
211
+ return address(assetManager()).functionDelegateCall(functionCall);
117
212
  }
118
213
  }
@@ -1,7 +1,8 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  pragma solidity ^0.8.0;
3
3
 
4
- import {WadRayMath} from "./WadRayMath.sol";
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 uint16(value / 1e14);
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 uint32(value / 10**(currency().decimals() - decimals));
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 = uint16(newValue);
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 == msg.sender || _policyPool.currency().allowance(payer, msg.sender) >= premium,
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: SCR limit exceeded");
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;