• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NexusMutual / smart-contracts / #778

23 Jul 2024 01:54PM UTC coverage: 87.593% (+4.3%) from 83.289%
#778

push

MilGard91
Run compilation in the storage layout extraction script if invoked from cli (thx @fvictorio)

1023 of 1274 branches covered (80.3%)

Branch coverage included in aggregate %.

2987 of 3304 relevant lines covered (90.41%)

170.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.43
/contracts/modules/cover/Cover.sol
1
// SPDX-License-Identifier: GPL-3.0-only
2

3
pragma solidity ^0.8.18;
4

5
import "@openzeppelin/contracts-v4/security/ReentrancyGuard.sol";
6
import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";
7
import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol";
8

9
import "../../abstract/MasterAwareV2.sol";
10
import "../../abstract/Multicall.sol";
11
import "../../interfaces/ICover.sol";
12
import "../../interfaces/ICoverNFT.sol";
13
import "../../interfaces/ICoverProducts.sol";
14
import "../../interfaces/IPool.sol";
15
import "../../interfaces/IStakingNFT.sol";
16
import "../../interfaces/IStakingPool.sol";
17
import "../../interfaces/IStakingPoolBeacon.sol";
18
import "../../interfaces/ICompleteStakingPoolFactory.sol";
19
import "../../interfaces/ITokenController.sol";
20
import "../../libraries/Math.sol";
21
import "../../libraries/SafeUintCast.sol";
22
import "../../libraries/StakingPoolLibrary.sol";
23

24
contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard, Multicall {
25
  using SafeERC20 for IERC20;
26
  using SafeUintCast for uint;
27

28
  /* ========== STATE VARIABLES ========== */
29

30
  // moved to cover products
31
  Product[] private _unused_products;
32
  ProductType[] private _unused_productTypes;
33

34
  mapping(uint => CoverData) private _coverData;
35

36
  // cover id => segment id => pool allocations array
37
  mapping(uint => mapping(uint => PoolAllocation[])) public coverSegmentAllocations;
38

39
  // moved to cover products
40
  mapping(uint => uint[]) private _unused_allowedPools;
41

42
  // Each cover has an array of segments. A new segment is created
43
  // every time a cover is edited to indicate the different cover periods.
44
  mapping(uint => CoverSegment[]) private _coverSegments;
45

46
  // assetId => { lastBucketUpdateId, totalActiveCoverInAsset }
47
  mapping(uint => ActiveCover) public activeCover;
48
  // assetId => bucketId => amount
49
  mapping(uint => mapping(uint => uint)) internal activeCoverExpirationBuckets;
50

51
  // moved to cover products
52
  mapping(uint => string) private _unused_productNames;
53
  mapping(uint => string) private _unused_productTypeNames;
54

55
  /* ========== CONSTANTS ========== */
56

57
  uint private constant GLOBAL_CAPACITY_RATIO = 20000; // 2
58
  uint private constant GLOBAL_REWARDS_RATIO = 5000; // 50%
59

60
  uint private constant COMMISSION_DENOMINATOR = 10000;
61
  uint private constant GLOBAL_CAPACITY_DENOMINATOR = 10_000;
62

63
  uint private constant MAX_COVER_PERIOD = 365 days;
64
  uint private constant MIN_COVER_PERIOD = 28 days;
65
  uint private constant BUCKET_SIZE = 7 days;
66

67
  uint public constant MAX_COMMISSION_RATIO = 3000; // 30%
68

69
  uint public constant GLOBAL_MIN_PRICE_RATIO = 100; // 1%
70

71
  uint private constant ONE_NXM = 1e18;
72

73
  uint private constant ETH_ASSET_ID = 0;
74
  uint private constant NXM_ASSET_ID = type(uint8).max;
75

76
  // internally we store capacity using 2 decimals
77
  // 1 nxm of capacity is stored as 100
78
  uint private constant ALLOCATION_UNITS_PER_NXM = 100;
79

80
  // given capacities have 2 decimals
81
  // smallest unit we can allocate is 1e18 / 100 = 1e16 = 0.01 NXM
82
  uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM;
83

84
  ICoverNFT public immutable override coverNFT;
85
  IStakingNFT public immutable override stakingNFT;
86
  ICompleteStakingPoolFactory public immutable override stakingPoolFactory;
87
  address public immutable stakingPoolImplementation;
88

89
  /* ========== CONSTRUCTOR ========== */
90

91
  constructor(
92
    ICoverNFT _coverNFT,
93
    IStakingNFT _stakingNFT,
94
    ICompleteStakingPoolFactory _stakingPoolFactory,
95
    address _stakingPoolImplementation
96
  ) {
97
    // in constructor we only initialize immutable fields
98
    coverNFT = _coverNFT;
9✔
99
    stakingNFT = _stakingNFT;
9✔
100
    stakingPoolFactory = _stakingPoolFactory;
9✔
101
    stakingPoolImplementation = _stakingPoolImplementation;
9✔
102
  }
103

104
  /* === MUTATIVE FUNCTIONS ==== */
105

106
  function buyCover(
107
    BuyCoverParams memory params,
108
    PoolAllocationRequest[] memory poolAllocationRequests
109
  ) external payable onlyMember nonReentrant whenNotPaused returns (uint coverId) {
110

111
    if (params.period < MIN_COVER_PERIOD) {
124✔
112
      revert CoverPeriodTooShort();
1✔
113
    }
114

115
    if (params.period > MAX_COVER_PERIOD) {
123✔
116
      revert CoverPeriodTooLong();
1✔
117
    }
118

119
    if (params.commissionRatio > MAX_COMMISSION_RATIO) {
122✔
120
      revert CommissionRateTooHigh();
1✔
121
    }
122

123
    if (params.amount == 0) {
121✔
124
      revert CoverAmountIsZero();
1✔
125
    }
126

127
    // can pay with cover asset or nxm only
128
    if (params.paymentAsset != params.coverAsset && params.paymentAsset != NXM_ASSET_ID) {
120✔
129
      revert InvalidPaymentAsset();
1✔
130
    }
131

132
    uint segmentId;
119✔
133

134
    AllocationRequest memory allocationRequest;
119✔
135
    {
119✔
136

137
      ICoverProducts _coverProducts = coverProducts();
119✔
138

139
      if (_coverProducts.getProductCount() <= params.productId) {
119✔
140
        revert ProductNotFound();
1✔
141
      }
142

143
      (
118✔
144
        Product memory product,
145
        ProductType memory productType
146
      ) = _coverProducts.getProductWithType(params.productId);
147

148
      if (product.isDeprecated) {
118✔
149
        revert ProductDeprecated();
3✔
150
      }
151

152
      if (!isCoverAssetSupported(params.coverAsset, product.coverAssets)) {
115✔
153
        revert CoverAssetNotSupported();
4✔
154
      }
155

156
      allocationRequest.productId = params.productId;
110✔
157
      allocationRequest.coverId = coverId;
110✔
158
      allocationRequest.period = params.period;
110✔
159
      allocationRequest.gracePeriod = productType.gracePeriod;
110✔
160
      allocationRequest.useFixedPrice = product.useFixedPrice;
110✔
161
      allocationRequest.globalCapacityRatio = GLOBAL_CAPACITY_RATIO;
110✔
162
      allocationRequest.capacityReductionRatio = product.capacityReductionRatio;
110✔
163
      allocationRequest.rewardRatio = GLOBAL_REWARDS_RATIO;
110✔
164
      allocationRequest.globalMinPrice = GLOBAL_MIN_PRICE_RATIO;
110✔
165
    }
166

167
    uint previousSegmentAmount;
110✔
168

169
    if (params.coverId == 0) {
110!
170

171
      // new cover
172
      coverId = coverNFT.mint(params.owner);
110✔
173
      _coverData[coverId] = CoverData(params.productId, params.coverAsset, 0 /* amountPaidOut */);
109✔
174

175
    } else {
176
      revert EditNotSupported();
×
177

178
      /*
179
      // existing cover
180
      coverId = params.coverId;
181

182
      if (!coverNFT.isApprovedOrOwner(msg.sender, coverId)) {
183
        revert OnlyOwnerOrApproved();
184
      }
185

186
      CoverData memory cover = _coverData[coverId];
187

188
      if (params.coverAsset != cover.coverAsset) {
189
        revert UnexpectedCoverAsset();
190
      }
191

192
      if (params.productId != cover.productId) {
193
        revert UnexpectedProductId();
194
      }
195

196
      segmentId = _coverSegments[coverId].length;
197
      CoverSegment memory lastSegment = coverSegmentWithRemainingAmount(coverId, segmentId - 1);
198

199
      // require last segment not to be expired
200
      if (lastSegment.start + lastSegment.period <= block.timestamp) {
201
        revert ExpiredCoversCannotBeEdited();
202
      }
203

204
      allocationRequest.previousStart = lastSegment.start;
205
      allocationRequest.previousExpiration = lastSegment.start + lastSegment.period;
206
      allocationRequest.previousRewardsRatio = lastSegment.globalRewardsRatio;
207

208
      // mark previous cover as ending now
209
      _coverSegments[coverId][segmentId - 1].period = (block.timestamp - lastSegment.start).toUint32();
210

211
      // remove cover amount from from expiration buckets
212
      uint bucketAtExpiry = Math.divCeil(lastSegment.start + lastSegment.period, BUCKET_SIZE);
213
      activeCoverExpirationBuckets[params.coverAsset][bucketAtExpiry] -= lastSegment.amount;
214
      previousSegmentAmount += lastSegment.amount;
215
      */
216
    }
217

218
    uint nxmPriceInCoverAsset = pool().getInternalTokenPriceInAssetAndUpdateTwap(params.coverAsset);
109✔
219
    allocationRequest.coverId = coverId;
109✔
220

221
    (uint coverAmountInCoverAsset, uint amountDueInNXM) = requestAllocation(
109✔
222
      allocationRequest,
223
      poolAllocationRequests,
224
      nxmPriceInCoverAsset,
225
      segmentId
226
    );
227

228
    if (coverAmountInCoverAsset < params.amount) {
109✔
229
      revert InsufficientCoverAmountAllocated();
2✔
230
    }
231

232
    _coverSegments[coverId].push(
107✔
233
      CoverSegment(
234
        coverAmountInCoverAsset.toUint96(), // cover amount in cover asset
235
        block.timestamp.toUint32(), // start
236
        params.period, // period
237
        allocationRequest.gracePeriod.toUint32(),
238
        GLOBAL_REWARDS_RATIO.toUint24(),
239
        GLOBAL_CAPACITY_RATIO.toUint24()
240
      )
241
    );
242

243
    _updateTotalActiveCoverAmount(params.coverAsset, coverAmountInCoverAsset, params.period, previousSegmentAmount);
107✔
244

245
    retrievePayment(
107✔
246
      amountDueInNXM,
247
      params.paymentAsset,
248
      nxmPriceInCoverAsset,
249
      params.maxPremiumInAsset,
250
      params.commissionRatio,
251
      params.commissionDestination
252
    );
253

254
    emit CoverEdited(coverId, params.productId, segmentId, msg.sender, params.ipfsData);
104✔
255
  }
256

257
  function expireCover(uint coverId) external {
258

259
    uint segmentId = _coverSegments[coverId].length - 1;
8✔
260
    CoverSegment memory lastSegment = coverSegmentWithRemainingAmount(coverId, segmentId);
8✔
261
    CoverData memory cover = _coverData[coverId];
8✔
262
    uint expiration = lastSegment.start + lastSegment.period;
8✔
263

264
    if (expiration > block.timestamp) {
8✔
265
      revert CoverNotYetExpired(coverId);
1✔
266
    }
267

268
    for (
7✔
269
      uint allocationIndex = 0;
270
      allocationIndex < coverSegmentAllocations[coverId][segmentId].length;
271
      allocationIndex++
272
    ) {
273
      PoolAllocation memory allocation = coverSegmentAllocations[coverId][segmentId][allocationIndex];
8✔
274
      AllocationRequest memory allocationRequest;
8✔
275
      // editing just the needed props for deallocation
276
      allocationRequest.productId = cover.productId;
8✔
277
      allocationRequest.allocationId = allocation.allocationId;
8✔
278
      allocationRequest.previousStart = lastSegment.start;
8✔
279
      allocationRequest.previousExpiration = expiration;
8✔
280

281
      stakingPool(allocation.poolId).requestAllocation(
8✔
282
        0, // amount
283
        0, // previous premium
284
        allocationRequest
285
      );
286
    }
287

288
    uint currentBucketId = block.timestamp / BUCKET_SIZE;
5✔
289
    uint bucketAtExpiry = Math.divCeil(expiration, BUCKET_SIZE);
5✔
290

291
    // if it expires in a future bucket
292
    if (currentBucketId < bucketAtExpiry) {
5✔
293
      // remove cover amount from expiration buckets and totalActiveCoverInAsset without updating last bucket id
294
      activeCoverExpirationBuckets[cover.coverAsset][bucketAtExpiry] -= lastSegment.amount;
4✔
295
      activeCover[cover.coverAsset].totalActiveCoverInAsset -= lastSegment.amount;
4✔
296
    }
297
  }
298

299
  function requestAllocation(
300
    AllocationRequest memory allocationRequest,
301
    PoolAllocationRequest[] memory poolAllocationRequests,
302
    uint nxmPriceInCoverAsset,
303
    uint segmentId
304
  ) internal returns (
305
    uint totalCoverAmountInCoverAsset,
306
    uint totalAmountDueInNXM
307
  ) {
308

309
    RequestAllocationVariables memory vars = RequestAllocationVariables(0, 0, 0, 0);
109✔
310
    uint totalCoverAmountInNXM;
109✔
311

312
    vars.previousPoolAllocationsLength = segmentId > 0
109✔
313
      ? coverSegmentAllocations[allocationRequest.coverId][segmentId - 1].length
314
      : 0;
315

316
    for (uint i = 0; i < poolAllocationRequests.length; i++) {
109✔
317
      // if there is a previous segment and this index is present on it
318
      if (vars.previousPoolAllocationsLength > i) {
113!
319
        PoolAllocation memory previousPoolAllocation =
×
320
                    coverSegmentAllocations[allocationRequest.coverId][segmentId - 1][i];
321

322
        // poolAllocationRequests must match the pools in the previous segment
323
        if (previousPoolAllocation.poolId != poolAllocationRequests[i].poolId) {
×
324
          revert UnexpectedPoolId();
×
325
        }
326

327
        // check if this request should be skipped, keeping the previous allocation
328
        if (poolAllocationRequests[i].skip) {
×
329
          coverSegmentAllocations[allocationRequest.coverId][segmentId].push(previousPoolAllocation);
×
330
          totalCoverAmountInNXM += previousPoolAllocation.coverAmountInNXM;
×
331
          continue;
×
332
        }
333

334
        vars.previousPremiumInNXM = previousPoolAllocation.premiumInNXM;
×
335
        vars.refund =
×
336
          previousPoolAllocation.premiumInNXM
337
          * (allocationRequest.previousExpiration - block.timestamp) // remaining period
338
          / (allocationRequest.previousExpiration - allocationRequest.previousStart); // previous period
339

340
        // get stored allocation id
341
        allocationRequest.allocationId = previousPoolAllocation.allocationId;
×
342
      } else {
343
        // request new allocation id
344
        allocationRequest.allocationId = 0;
113✔
345
      }
346

347
      // converting asset amount to nxm and rounding up to the nearest NXM_PER_ALLOCATION_UNIT
348
      uint coverAmountInNXM = Math.roundUp(
113✔
349
        Math.divCeil(poolAllocationRequests[i].coverAmountInAsset * ONE_NXM, nxmPriceInCoverAsset),
350
        NXM_PER_ALLOCATION_UNIT
351
      );
352

353
      (uint premiumInNXM, uint allocationId) = stakingPool(poolAllocationRequests[i].poolId).requestAllocation(
113✔
354
        coverAmountInNXM,
355
        vars.previousPremiumInNXM,
356
        allocationRequest
357
      );
358

359
      // omit deallocated pools from the segment
360
      if (coverAmountInNXM != 0) {
113✔
361
        coverSegmentAllocations[allocationRequest.coverId][segmentId].push(
112✔
362
          PoolAllocation(
363
            poolAllocationRequests[i].poolId,
364
            coverAmountInNXM.toUint96(),
365
            premiumInNXM.toUint96(),
366
            allocationId.toUint24()
367
          )
368
        );
369
      }
370

371
      totalAmountDueInNXM += (vars.refund >= premiumInNXM ? 0 : premiumInNXM - vars.refund);
113✔
372
      totalCoverAmountInNXM += coverAmountInNXM;
113✔
373
    }
374

375
    totalCoverAmountInCoverAsset = totalCoverAmountInNXM * nxmPriceInCoverAsset / ONE_NXM;
109✔
376

377
    return (totalCoverAmountInCoverAsset, totalAmountDueInNXM);
109✔
378
  }
379

380
  function retrievePayment(
381
    uint premiumInNxm,
382
    uint paymentAsset,
383
    uint nxmPriceInCoverAsset,
384
    uint maxPremiumInAsset,
385
    uint16 commissionRatio,
386
    address commissionDestination
387
  ) internal {
388

389
    if (paymentAsset != ETH_ASSET_ID && msg.value > 0) {
107!
390
      revert UnexpectedEthSent();
×
391
    }
392

393
    // NXM payment
394
    if (paymentAsset == NXM_ASSET_ID) {
107✔
395
      uint commissionInNxm;
3✔
396

397
      if (commissionRatio > 0) {
3!
398
        commissionInNxm = (premiumInNxm * COMMISSION_DENOMINATOR / (COMMISSION_DENOMINATOR - commissionRatio)) - premiumInNxm;
3✔
399
      }
400

401
      if (premiumInNxm + commissionInNxm > maxPremiumInAsset) {
3✔
402
        revert PriceExceedsMaxPremiumInAsset();
1✔
403
      }
404

405
      ITokenController _tokenController = tokenController();
2✔
406
      _tokenController.burnFrom(msg.sender, premiumInNxm);
2✔
407

408
      if (commissionInNxm > 0) {
2!
409
        // commission transfer reverts if the commissionDestination is not a member
410
        _tokenController.operatorTransfer(msg.sender, commissionDestination, commissionInNxm);
2✔
411
      }
412

413
      return;
2✔
414
    }
415

416
    IPool _pool = pool();
104✔
417
    uint premiumInPaymentAsset = nxmPriceInCoverAsset * premiumInNxm / ONE_NXM;
104✔
418
    uint commission = (premiumInPaymentAsset * COMMISSION_DENOMINATOR / (COMMISSION_DENOMINATOR - commissionRatio)) - premiumInPaymentAsset;
104✔
419
    uint premiumWithCommission = premiumInPaymentAsset + commission;
104✔
420

421
    if (premiumWithCommission > maxPremiumInAsset) {
104✔
422
      revert PriceExceedsMaxPremiumInAsset();
1✔
423
    }
424

425
    // ETH payment
426
    if (paymentAsset == ETH_ASSET_ID) {
103✔
427

428
      if (msg.value < premiumWithCommission) {
68!
429
        revert InsufficientEthSent();
×
430
      }
431

432
      uint remainder = msg.value - premiumWithCommission;
68✔
433

434
      {
68✔
435
        // send premium in eth to the pool
436
        // solhint-disable-next-line avoid-low-level-calls
437
        (bool ok, /* data */) = address(_pool).call{value: premiumInPaymentAsset}("");
68✔
438
        if (!ok) {
68!
439
          revert SendingEthToPoolFailed();
×
440
        }
441
      }
442

443
      // send commission
444
      if (commission > 0) {
68✔
445
        (bool ok, /* data */) = address(commissionDestination).call{value: commission}("");
2✔
446
        if (!ok) {
2✔
447
          revert SendingEthToCommissionDestinationFailed();
1✔
448
        }
449
      }
450

451
      if (remainder > 0) {
67✔
452
        // solhint-disable-next-line avoid-low-level-calls
453
        (bool ok, /* data */) = address(msg.sender).call{value: remainder}("");
54✔
454
        if (!ok) {
54!
455
          revert ReturningEthRemainderToSenderFailed();
×
456
        }
457
      }
458

459
      return;
67✔
460
    }
461

462
    address coverAsset = _pool.getAsset(paymentAsset).assetAddress;
35✔
463
    IERC20 token = IERC20(coverAsset);
35✔
464
    token.safeTransferFrom(msg.sender, address(_pool), premiumInPaymentAsset);
35✔
465

466
    if (commission > 0) {
35✔
467
      token.safeTransferFrom(msg.sender, commissionDestination, commission);
2✔
468
    }
469
  }
470

471
  function updateTotalActiveCoverAmount(uint coverAsset) public {
472
    _updateTotalActiveCoverAmount(coverAsset, 0, 0, 0);
1✔
473
  }
474

475
  function _updateTotalActiveCoverAmount(
476
    uint coverAsset,
477
    uint newCoverAmountInAsset,
478
    uint coverPeriod,
479
    uint previousCoverSegmentAmount
480
  ) internal {
481
    ActiveCover memory _activeCover = activeCover[coverAsset];
108✔
482

483
    uint currentBucketId = block.timestamp / BUCKET_SIZE;
108✔
484
    uint totalActiveCover = _activeCover.totalActiveCoverInAsset;
108✔
485

486
    if (totalActiveCover != 0) {
108✔
487
      totalActiveCover -= getExpiredCoverAmount(
19✔
488
        coverAsset,
489
        _activeCover.lastBucketUpdateId,
490
        currentBucketId
491
      );
492
    }
493

494
    totalActiveCover -= previousCoverSegmentAmount;
108✔
495
    totalActiveCover += newCoverAmountInAsset;
108✔
496

497
    _activeCover.lastBucketUpdateId = currentBucketId.toUint64();
108✔
498
    _activeCover.totalActiveCoverInAsset = totalActiveCover.toUint192();
108✔
499

500
    // update total active cover in storage
501
    activeCover[coverAsset] = _activeCover;
108✔
502

503
    // update amount to expire at the end of this cover segment
504
    uint bucketAtExpiry = Math.divCeil(block.timestamp + coverPeriod, BUCKET_SIZE);
108✔
505
    activeCoverExpirationBuckets[coverAsset][bucketAtExpiry] += newCoverAmountInAsset;
108✔
506
  }
507

508
  // Gets the total amount of active cover that is currently expired for this asset
509
  function getExpiredCoverAmount(
510
    uint coverAsset,
511
    uint lastUpdateId,
512
    uint currentBucketId
513
  ) internal view returns (uint amountExpired) {
514

515
    while (lastUpdateId < currentBucketId) {
67✔
516
      ++lastUpdateId;
104✔
517
      amountExpired += activeCoverExpirationBuckets[coverAsset][lastUpdateId];
104✔
518
    }
519

520
    return amountExpired;
67✔
521
  }
522

523
  function burnStake(
524
    uint coverId,
525
    uint segmentId,
526
    uint payoutAmountInAsset
527
  ) external onlyInternal override returns (address /* coverOwner */) {
528

529
    CoverData storage cover = _coverData[coverId];
48✔
530
    ActiveCover storage _activeCover = activeCover[cover.coverAsset];
48✔
531
    CoverSegment memory segment = coverSegmentWithRemainingAmount(coverId, segmentId);
48✔
532
    PoolAllocation[] storage allocations = coverSegmentAllocations[coverId][segmentId];
48✔
533

534
    // update expired buckets and calculate the amount of active cover that should be burned
535
    {
48✔
536
      uint coverAsset = cover.coverAsset;
48✔
537
      uint lastUpdateBucketId = _activeCover.lastBucketUpdateId;
48✔
538
      uint currentBucketId = block.timestamp / BUCKET_SIZE;
48✔
539

540
      uint burnedSegmentBucketId = Math.divCeil((segment.start + segment.period), BUCKET_SIZE);
48✔
541
      uint activeCoverToExpire = getExpiredCoverAmount(coverAsset, lastUpdateBucketId, currentBucketId);
48✔
542

543
      // if the segment has not expired - it's still accounted for in total active cover
544
      if (burnedSegmentBucketId > currentBucketId) {
48!
545
        uint amountToSubtract = Math.min(payoutAmountInAsset, segment.amount);
48✔
546
        activeCoverToExpire += amountToSubtract;
48✔
547
        activeCoverExpirationBuckets[coverAsset][burnedSegmentBucketId] -= amountToSubtract.toUint192();
48✔
548
      }
549

550
      _activeCover.totalActiveCoverInAsset -= activeCoverToExpire.toUint192();
48✔
551
      _activeCover.lastBucketUpdateId = currentBucketId.toUint64();
48✔
552
    }
553

554
    // increase amountPaidOut only *after* you read the segment
555
    cover.amountPaidOut += payoutAmountInAsset.toUint96();
48✔
556

557
    for (uint i = 0; i < allocations.length; i++) {
48✔
558
      PoolAllocation memory allocation = allocations[i];
51✔
559

560
      uint deallocationAmountInNXM = allocation.coverAmountInNXM * payoutAmountInAsset / segment.amount;
51✔
561
      uint burnAmountInNxm = deallocationAmountInNXM * GLOBAL_CAPACITY_DENOMINATOR / segment.globalCapacityRatio;
51✔
562

563
      allocations[i].coverAmountInNXM -= deallocationAmountInNXM.toUint96();
51✔
564
      allocations[i].premiumInNXM -= (allocation.premiumInNXM * payoutAmountInAsset / segment.amount).toUint96();
51✔
565

566
      BurnStakeParams memory params = BurnStakeParams(
51✔
567
        allocation.allocationId,
568
        cover.productId,
569
        segment.start,
570
        segment.period,
571
        deallocationAmountInNXM
572
      );
573

574
      uint poolId = allocations[i].poolId;
51✔
575
      stakingPool(poolId).burnStake(burnAmountInNxm, params);
51✔
576
    }
577

578
    return coverNFT.ownerOf(coverId);
48✔
579
  }
580

581
  /* ========== VIEWS ========== */
582

583
  function coverData(uint coverId) external override view returns (CoverData memory) {
584
    return _coverData[coverId];
79✔
585
  }
586

587
  function coverSegmentWithRemainingAmount(
588
    uint coverId,
589
    uint segmentId
590
  ) public override view returns (CoverSegment memory) {
591
    CoverSegment memory segment = _coverSegments[coverId][segmentId];
146✔
592
    uint96 amountPaidOut = _coverData[coverId].amountPaidOut;
146✔
593
    segment.amount = segment.amount >= amountPaidOut
146✔
594
      ? segment.amount - amountPaidOut
595
      : 0;
596
    return segment;
146✔
597
  }
598

599
  function coverSegments(uint coverId) external override view returns (CoverSegment[] memory) {
600
    return _coverSegments[coverId];
1✔
601
  }
602

603
  function coverSegmentsCount(uint coverId) external override view returns (uint) {
604
    return _coverSegments[coverId].length;
7✔
605
  }
606

607
  function coverDataCount() external override view returns (uint) {
608
    return coverNFT.totalSupply();
27✔
609
  }
610

611
  /* ========== COVER ASSETS HELPERS ========== */
612

613
  function recalculateActiveCoverInAsset(uint coverAsset) public {
614
    uint currentBucketId = block.timestamp / BUCKET_SIZE;
×
615
    uint totalActiveCover = 0;
×
616
    uint yearlyBucketsCount = Math.divCeil(MAX_COVER_PERIOD, BUCKET_SIZE);
×
617

618
    for (uint i = 1; i <= yearlyBucketsCount; i++) {
×
619
      uint bucketId = currentBucketId + i;
×
620
      totalActiveCover += activeCoverExpirationBuckets[coverAsset][bucketId];
×
621
    }
622

623
    activeCover[coverAsset] = ActiveCover(totalActiveCover.toUint192(), currentBucketId.toUint64());
×
624
  }
625

626
  function totalActiveCoverInAsset(uint assetId) public view returns (uint) {
627
    return uint(activeCover[assetId].totalActiveCoverInAsset);
372✔
628
  }
629

630
  function getGlobalCapacityRatio() external pure returns (uint) {
631
    return GLOBAL_CAPACITY_RATIO;
3✔
632
  }
633

634
  function getGlobalRewardsRatio() external pure returns (uint) {
635
    return GLOBAL_REWARDS_RATIO;
2✔
636
  }
637

638
  function getGlobalMinPriceRatio() external pure returns (uint) {
639
    return GLOBAL_MIN_PRICE_RATIO;
23✔
640
  }
641

642
  function getGlobalCapacityAndPriceRatios() external pure returns (
643
    uint _globalCapacityRatio,
644
    uint _globalMinPriceRatio
645
  ) {
646
    _globalCapacityRatio = GLOBAL_CAPACITY_RATIO;
56✔
647
    _globalMinPriceRatio = GLOBAL_MIN_PRICE_RATIO;
56✔
648
  }
649

650
  function isCoverAssetSupported(uint assetId, uint productCoverAssetsBitmap) internal view returns (bool) {
651

652
    if (
115✔
653
      // product does not use default cover assets
654
      productCoverAssetsBitmap != 0 &&
655
      // asset id is not in the product's cover assets bitmap
656
      ((1 << assetId) & productCoverAssetsBitmap == 0)
657
    ) {
658
      return false;
1✔
659
    }
660

661
    Asset memory asset = pool().getAsset(assetId);
114✔
662

663
    return asset.isCoverAsset && !asset.isAbandoned;
113✔
664
  }
665

666
  function stakingPool(uint poolId) public view returns (IStakingPool) {
667
    return IStakingPool(
172✔
668
      StakingPoolLibrary.getAddress(address(stakingPoolFactory), poolId)
669
    );
670
  }
671

672
  function changeCoverNFTDescriptor(address _coverNFTDescriptor) external onlyAdvisoryBoard {
673
    coverNFT.changeNFTDescriptor(_coverNFTDescriptor);
1✔
674
  }
675

676
  function changeStakingNFTDescriptor(address _stakingNFTDescriptor) external onlyAdvisoryBoard {
677
    stakingNFT.changeNFTDescriptor(_stakingNFTDescriptor);
1✔
678
  }
679

680
  function changeStakingPoolFactoryOperator() external {
681
    address _operator = master.getLatestAddress("SP");
×
682
    stakingPoolFactory.changeOperator(_operator);
×
683
  }
684

685
  /* ========== DEPENDENCIES ========== */
686

687
  function pool() internal view returns (IPool) {
688
    return IPool(internalContracts[uint(ID.P1)]);
327✔
689
  }
690

691
  function tokenController() internal view returns (ITokenController) {
692
    return ITokenController(internalContracts[uint(ID.TC)]);
2✔
693
  }
694

695
  function memberRoles() internal view returns (IMemberRoles) {
696
    return IMemberRoles(internalContracts[uint(ID.MR)]);
×
697
  }
698

699
  function coverProducts() internal view returns (ICoverProducts) {
700
    return ICoverProducts(internalContracts[uint(ID.CP)]);
119✔
701
  }
702

703
  function changeDependentContractAddress() external override {
704
    internalContracts[uint(ID.P1)] = master.getLatestAddress("P1");
16✔
705
    internalContracts[uint(ID.TC)] = master.getLatestAddress("TC");
16✔
706
    internalContracts[uint(ID.MR)] = master.getLatestAddress("MR");
16✔
707
    internalContracts[uint(ID.CP)] = master.getLatestAddress("CP");
16✔
708
  }
709
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc