• 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

96.17
/contracts/modules/staking/StakingProducts.sol
1
// SPDX-License-Identifier: GPL-3.0-only
2

3
pragma solidity ^0.8.18;
4

5
import "../../abstract/MasterAwareV2.sol";
6
import "../../abstract/Multicall.sol";
7
import "../../interfaces/ICover.sol";
8
import "../../interfaces/ICoverProducts.sol";
9
import "../../interfaces/IStakingProducts.sol";
10
import "../../interfaces/ITokenController.sol";
11
import "../../libraries/Math.sol";
12
import "../../libraries/SafeUintCast.sol";
13
import "../../libraries/StakingPoolLibrary.sol";
14

15
contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall {
16
  using SafeUintCast for uint;
17

18
  uint public constant SURGE_PRICE_RATIO = 2 ether;
19
  uint public constant SURGE_THRESHOLD_RATIO = 90_00; // 90.00%
20
  uint public constant SURGE_THRESHOLD_DENOMINATOR = 100_00; // 100.00%
21
  // base price bump
22
  // +0.2% for each 1% of capacity used, ie +20% for 100%
23
  uint public constant PRICE_BUMP_RATIO = 20_00; // 20%
24
  // bumped price smoothing
25
  // 0.5% per day
26
  uint public constant PRICE_CHANGE_PER_DAY = 200; // 2%
27
  uint public constant INITIAL_PRICE_DENOMINATOR = 100_00;
28
  uint public constant TARGET_PRICE_DENOMINATOR = 100_00;
29
  uint public constant MAX_TOTAL_WEIGHT = 20_00; // 20x
30

31
  // The 3 constants below are also used in the StakingPool contract
32
  uint public constant TRANCHE_DURATION = 91 days;
33
  uint public constant MAX_ACTIVE_TRANCHES = 8; // 7 whole quarters + 1 partial quarter
34
  uint public constant WEIGHT_DENOMINATOR = 100;
35

36
  // denominators for cover contract parameters
37
  uint public constant GLOBAL_CAPACITY_DENOMINATOR = 100_00;
38
  uint public constant CAPACITY_REDUCTION_DENOMINATOR = 100_00;
39

40
  uint public constant ONE_NXM = 1 ether;
41
  uint public constant ALLOCATION_UNITS_PER_NXM = 100;
42
  uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM;
43

44
  // pool id => product id => Product
45
  mapping(uint => mapping(uint => StakedProduct)) private _products;
46
  // pool id => { totalEffectiveWeight, totalTargetWeight }
47
  mapping(uint => Weights) public weights;
48

49
  address public immutable coverContract;
50
  address public immutable stakingPoolFactory;
51

52
  constructor(address _coverContract, address _stakingPoolFactory) {
53
    coverContract = _coverContract;
6✔
54
    stakingPoolFactory = _stakingPoolFactory;
6✔
55
  }
56

57
  function getProductTargetWeight(uint poolId, uint productId) external view override returns (uint) {
58
    return uint(_products[poolId][productId].targetWeight);
403✔
59
  }
60

61
  function getTotalTargetWeight(uint poolId) external override view returns (uint) {
62
    return weights[poolId].totalTargetWeight;
23✔
63
  }
64

65
  function getTotalEffectiveWeight(uint poolId) external override view returns (uint) {
66
    return weights[poolId].totalEffectiveWeight;
28✔
67
  }
68

69
  function getProduct(uint poolId, uint productId) external override view returns (
70
    uint lastEffectiveWeight,
71
    uint targetWeight,
72
    uint targetPrice,
73
    uint bumpedPrice,
74
    uint bumpedPriceUpdateTime
75
  ) {
76
    StakedProduct memory product = _products[poolId][productId];
42✔
77
    return (
42✔
78
      product.lastEffectiveWeight,
79
      product.targetWeight,
80
      product.targetPrice,
81
      product.bumpedPrice,
82
      product.bumpedPriceUpdateTime
83
    );
84
  }
85

86
  function recalculateEffectiveWeights(uint poolId, uint[] calldata productIds) external {
87

88
    IStakingPool _stakingPool = stakingPool(poolId);
14✔
89

90
    uint[] memory capacityReductionRatios = coverProducts().getCapacityReductionRatios(productIds);
14✔
91
    uint globalCapacityRatio = cover().getGlobalCapacityRatio();
14✔
92

93
    uint _totalEffectiveWeight = weights[poolId].totalEffectiveWeight;
14✔
94

95
    for (uint i = 0; i < productIds.length; i++) {
14✔
96
      uint productId = productIds[i];
290✔
97
      StakedProduct memory _product = _products[poolId][productId];
290✔
98

99
      uint16 previousEffectiveWeight = _product.lastEffectiveWeight;
290✔
100
      _product.lastEffectiveWeight = _getEffectiveWeight(
290✔
101
        _stakingPool,
102
        productId,
103
        _product.targetWeight,
104
        globalCapacityRatio,
105
        capacityReductionRatios[i]
106
      );
107
      _totalEffectiveWeight = _totalEffectiveWeight - previousEffectiveWeight + _product.lastEffectiveWeight;
290✔
108
      _products[poolId][productId] = _product;
290✔
109
    }
110

111
    weights[poolId].totalEffectiveWeight = _totalEffectiveWeight.toUint32();
14✔
112
  }
113

114
  function recalculateEffectiveWeightsForAllProducts(uint poolId) external {
115

116
    ICoverProducts _coverProducts = coverProducts();
3✔
117
    IStakingPool _stakingPool = stakingPool(poolId);
3✔
118

119
    uint productsCount = _coverProducts.getProductCount();
3✔
120

121
    // initialize array for all possible products
122
    uint[] memory productIdsRaw = new uint[](productsCount);
3✔
123
    uint stakingPoolProductCount;
3✔
124

125
    // filter out products that are not in this pool
126
    for (uint i = 0; i < productsCount; i++) {
3✔
127
      if (_products[poolId][i].bumpedPriceUpdateTime == 0) {
24✔
128
        continue;
17✔
129
      }
130
      productIdsRaw[stakingPoolProductCount++] = i;
7✔
131
    }
132

133
    // use resized array
134
    uint[] memory productIds = new uint[](stakingPoolProductCount);
3✔
135

136
    for (uint i = 0; i < stakingPoolProductCount; i++) {
3✔
137
      productIds[i] = productIdsRaw[i];
7✔
138
    }
139

140
    uint globalCapacityRatio = cover().getGlobalCapacityRatio();
3✔
141
    uint[] memory capacityReductionRatios = _coverProducts.getCapacityReductionRatios(productIds);
3✔
142

143
    uint _totalEffectiveWeight;
3✔
144

145
    for (uint i = 0; i < stakingPoolProductCount; i++) {
3✔
146
      uint productId = productIds[i];
7✔
147
      StakedProduct memory _product = _products[poolId][productId];
7✔
148

149
      // Get current effectiveWeight
150
      _product.lastEffectiveWeight = _getEffectiveWeight(
7✔
151
        _stakingPool,
152
        productId,
153
        _product.targetWeight,
154
        globalCapacityRatio,
155
        capacityReductionRatios[i]
156
      );
157
      _totalEffectiveWeight += _product.lastEffectiveWeight;
7✔
158
      _products[poolId][productId] = _product;
7✔
159
    }
160

161
    weights[poolId].totalEffectiveWeight = _totalEffectiveWeight.toUint32();
3✔
162
  }
163

164
  function setProducts(uint poolId, StakedProductParam[] memory params) external {
165

166
    IStakingPool _stakingPool = stakingPool(poolId);
117✔
167

168
    if (msg.sender != _stakingPool.manager()) {
117✔
169
      revert OnlyManager();
1✔
170
    }
171

172
    (
115✔
173
      uint globalCapacityRatio,
174
      uint globalMinPriceRatio
175
    ) = ICover(coverContract).getGlobalCapacityAndPriceRatios();
176

177
    uint[] memory initialPriceRatios;
115✔
178
    uint[] memory capacityReductionRatios;
115✔
179

180
    {
115✔
181
      uint numProducts = params.length;
115✔
182
      uint[] memory productIds = new uint[](numProducts);
115✔
183

184
      for (uint i = 0; i < numProducts; i++) {
115✔
185
        productIds[i] = params[i].productId;
808✔
186
      }
187

188
      ICoverProducts _coverProducts = coverProducts();
115✔
189

190
      // reverts if poolId is not allowed for any of these products
191
      _coverProducts.requirePoolIsAllowed(productIds, poolId);
115✔
192

193
      initialPriceRatios = _coverProducts.getInitialPrices(productIds);
113✔
194
      capacityReductionRatios = _coverProducts.getCapacityReductionRatios(productIds);
112✔
195
    }
196

197
    Weights memory _weights = weights[poolId];
112✔
198
    bool targetWeightIncreased;
112✔
199

200
    for (uint i = 0; i < params.length; i++) {
112✔
201
      StakedProductParam memory _param = params[i];
805✔
202
      StakedProduct memory _product = _products[poolId][_param.productId];
805✔
203

204
      bool isNewProduct = _product.bumpedPriceUpdateTime == 0;
805✔
205

206
      // if this is a new product
207
      if (isNewProduct) {
805✔
208
        // initialize the bumpedPrice
209
        _product.bumpedPrice = initialPriceRatios[i].toUint96();
446✔
210
        _product.bumpedPriceUpdateTime = uint32(block.timestamp);
446✔
211
        // and make sure we set the price and the target weight
212
        if (!_param.setTargetPrice) {
446✔
213
          revert MustSetPriceForNewProducts();
1✔
214
        }
215
        if (!_param.setTargetWeight) {
445!
216
          revert MustSetWeightForNewProducts();
×
217
        }
218
      }
219

220
      if (_param.setTargetPrice) {
804✔
221

222
        if (_param.targetPrice > TARGET_PRICE_DENOMINATOR) {
800✔
223
          revert TargetPriceTooHigh();
1✔
224
        }
225

226
        if (_param.targetPrice < globalMinPriceRatio) {
799✔
227
          revert TargetPriceBelowMin();
1✔
228
        }
229

230
        // if this is an existing product, when the target price is updated we need to calculate the
231
        // current base price using the old target price and update the bumped price to that value
232
        // uses the same logic as calculatePremium()
233
        if (!isNewProduct) {
798✔
234

235
          // apply price change per day towards previous target price
236
          uint newBumpedPrice = getBasePrice(
355✔
237
            _product.bumpedPrice,
238
            _product.bumpedPriceUpdateTime,
239
            _product.targetPrice,
240
            block.timestamp
241
          );
242

243
          // update product with new bumped price and bumped price update time
244
          _product.bumpedPrice = newBumpedPrice.toUint96();
355✔
245
          _product.bumpedPriceUpdateTime = block.timestamp.toUint32();
355✔
246
        }
247

248
        _product.targetPrice = _param.targetPrice;
798✔
249
      }
250

251
      // if setTargetWeight is set - effective weight must be recalculated
252
      if (_param.setTargetWeight && !_param.recalculateEffectiveWeight) {
802✔
253
        revert MustRecalculateEffectiveWeight();
2✔
254
      }
255

256
      // Must recalculate effectiveWeight to adjust targetWeight
257
      if (_param.recalculateEffectiveWeight) {
800!
258

259
        if (_param.setTargetWeight) {
800✔
260
          if (_param.targetWeight > WEIGHT_DENOMINATOR) {
798✔
261
            revert TargetWeightTooHigh();
1✔
262
          }
263

264
          // totalEffectiveWeight cannot be above the max unless target  weight is not increased
265
          if (!targetWeightIncreased) {
797✔
266
            targetWeightIncreased = _param.targetWeight > _product.targetWeight;
165✔
267
          }
268
          _weights.totalTargetWeight = _weights.totalTargetWeight - _product.targetWeight + _param.targetWeight;
797✔
269
          _product.targetWeight = _param.targetWeight;
797✔
270
        }
271

272
        // subtract the previous effective weight
273
        _weights.totalEffectiveWeight -= _product.lastEffectiveWeight;
799✔
274

275
        _product.lastEffectiveWeight = _getEffectiveWeight(
799✔
276
          _stakingPool,
277
          _param.productId,
278
          _product.targetWeight,
279
          globalCapacityRatio,
280
          capacityReductionRatios[i]
281
        );
282

283
        // add the new effective weight
284
        _weights.totalEffectiveWeight += _product.lastEffectiveWeight;
799✔
285
      }
286

287
      // sstore
288
      _products[poolId][_param.productId] = _product;
799✔
289

290
      emit ProductUpdated(_param.productId, _param.targetWeight, _param.targetPrice);
799✔
291
    }
292

293
    if (_weights.totalTargetWeight > MAX_TOTAL_WEIGHT) {
106✔
294
      revert TotalTargetWeightExceeded();
3✔
295
    }
296

297
    if (targetWeightIncreased) {
103✔
298
      if (_weights.totalEffectiveWeight > MAX_TOTAL_WEIGHT) {
66✔
299
        revert TotalEffectiveWeightExceeded();
3✔
300
      }
301
    }
302

303
    weights[poolId] = _weights;
100✔
304
  }
305

306
  function getEffectiveWeight(
307
    uint poolId,
308
    uint productId,
309
    uint targetWeight,
310
    uint globalCapacityRatio,
311
    uint capacityReductionRatio
312
  ) public view returns (uint effectiveWeight) {
313

314
    IStakingPool _stakingPool = stakingPool(poolId);
14✔
315

316
    return _getEffectiveWeight(
14✔
317
      _stakingPool,
318
      productId,
319
      targetWeight,
320
      globalCapacityRatio,
321
      capacityReductionRatio
322
    );
323
  }
324

325
  function _getEffectiveWeight(
326
    IStakingPool _stakingPool,
327
    uint productId,
328
    uint targetWeight,
329
    uint globalCapacityRatio,
330
    uint capacityReductionRatio
331
  ) internal view returns (uint16 effectiveWeight) {
332

333
    uint activeStake = _stakingPool.getActiveStake();
1,110✔
334
    uint multiplier = globalCapacityRatio * (CAPACITY_REDUCTION_DENOMINATOR - capacityReductionRatio);
1,110✔
335
    uint denominator = GLOBAL_CAPACITY_DENOMINATOR * CAPACITY_REDUCTION_DENOMINATOR;
1,110✔
336
    uint totalCapacity = activeStake * multiplier / denominator / NXM_PER_ALLOCATION_UNIT;
1,110✔
337

338
    if (totalCapacity == 0) {
1,110✔
339
      return targetWeight.toUint16();
164✔
340
    }
341

342
    uint[] memory activeAllocations = _stakingPool.getActiveAllocations(productId);
946✔
343
    uint totalAllocation = Math.sum(activeAllocations);
946✔
344
    uint actualWeight = Math.min(totalAllocation * WEIGHT_DENOMINATOR / totalCapacity, type(uint16).max);
946✔
345

346
    return Math.max(targetWeight, actualWeight).toUint16();
946✔
347
  }
348

349
  /* pricing code */
350
  function getPremium(
351
    uint poolId,
352
    uint productId,
353
    uint period,
354
    uint coverAmount,
355
    uint initialCapacityUsed,
356
    uint totalCapacity,
357
    uint globalMinPrice,
358
    bool useFixedPrice,
359
    uint nxmPerAllocationUnit,
360
    uint allocationUnitsPerNXM
361
  ) public returns (uint premium) {
362

363
    if (msg.sender != StakingPoolLibrary.getAddress(stakingPoolFactory, poolId)) {
401!
364
      revert OnlyStakingPool();
×
365
    }
366

367
    StakedProduct memory product = _products[poolId][productId];
401✔
368
    uint targetPrice = Math.max(product.targetPrice, globalMinPrice);
401✔
369

370
    if (useFixedPrice) {
401✔
371
      return calculateFixedPricePremium(coverAmount, period, targetPrice, nxmPerAllocationUnit, TARGET_PRICE_DENOMINATOR);
3✔
372
    }
373

374
    (premium, product) = calculatePremium(
398✔
375
      product,
376
      period,
377
      coverAmount,
378
      initialCapacityUsed,
379
      totalCapacity,
380
      targetPrice,
381
      block.timestamp,
382
      nxmPerAllocationUnit,
383
      allocationUnitsPerNXM,
384
      TARGET_PRICE_DENOMINATOR
385
    );
386

387
    // sstore
388
    _products[poolId][productId] = product;
398✔
389

390
    return premium;
398✔
391
  }
392

393
  function calculateFixedPricePremium(
394
    uint coverAmount,
395
    uint period,
396
    uint fixedPrice,
397
    uint nxmPerAllocationUnit,
398
    uint targetPriceDenominator
399
  ) public pure returns (uint) {
400

401
    uint premiumPerYear =
3✔
402
    coverAmount
403
    * nxmPerAllocationUnit
404
    * fixedPrice
405
    / targetPriceDenominator;
406

407
    return premiumPerYear * period / 365 days;
3✔
408
  }
409

410
  function getBasePrice(
411
    uint productBumpedPrice,
412
    uint productBumpedPriceUpdateTime,
413
    uint targetPrice,
414
    uint timestamp
415
  ) public pure returns (uint basePrice) {
416

417
    // use previously recorded bumped price and apply time based smoothing towards target price
418
    uint timeSinceLastUpdate = timestamp - productBumpedPriceUpdateTime;
753✔
419
    uint priceDrop = PRICE_CHANGE_PER_DAY * timeSinceLastUpdate / 1 days;
753✔
420

421
    // basePrice = max(targetPrice, bumpedPrice - priceDrop)
422
    // rewritten to avoid underflow
423
    return productBumpedPrice < targetPrice + priceDrop
753✔
424
      ? targetPrice
425
      : productBumpedPrice - priceDrop;
426
  }
427

428
  function calculatePremium(
429
    StakedProduct memory product,
430
    uint period,
431
    uint coverAmount,
432
    uint initialCapacityUsed,
433
    uint totalCapacity,
434
    uint targetPrice,
435
    uint currentBlockTimestamp,
436
    uint nxmPerAllocationUnit,
437
    uint allocationUnitsPerNxm,
438
    uint targetPriceDenominator
439
  ) public pure returns (uint premium, StakedProduct memory) {
440

441
    // calculate the bumped price by applying the price bump
442
    uint priceBump = PRICE_BUMP_RATIO * coverAmount / totalCapacity;
398✔
443

444
    // apply change in price-per-day towards the target price
445
    uint basePrice = getBasePrice(
398✔
446
       product.bumpedPrice,
447
       product.bumpedPriceUpdateTime,
448
       targetPrice,
449
       currentBlockTimestamp
450
     );
451

452
    // update product with new bumped price and timestamp
453
    product.bumpedPrice = (basePrice + priceBump).toUint96();
398✔
454
    product.bumpedPriceUpdateTime = uint32(currentBlockTimestamp);
398✔
455

456
    // use calculated base price and apply surge pricing if applicable
457
    uint premiumPerYear = calculatePremiumPerYear(
398✔
458
      basePrice,
459
      coverAmount,
460
      initialCapacityUsed,
461
      totalCapacity,
462
      nxmPerAllocationUnit,
463
      allocationUnitsPerNxm,
464
      targetPriceDenominator
465
    );
466

467
    // calculate the premium for the requested period
468
    return (premiumPerYear * period / 365 days, product);
398✔
469
  }
470

471
  function calculatePremiumPerYear(
472
    uint basePrice,
473
    uint coverAmount,
474
    uint initialCapacityUsed,
475
    uint totalCapacity,
476
    uint nxmPerAllocationUnit,
477
    uint allocationUnitsPerNxm,
478
    uint targetPriceDenominator
479
  ) public pure returns (uint) {
480
    // cover amount has 2 decimals (100 = 1 unit)
481
    // scale coverAmount to 18 decimals and apply price percentage
482
    uint basePremium = coverAmount * nxmPerAllocationUnit * basePrice / targetPriceDenominator;
398✔
483
    uint finalCapacityUsed = initialCapacityUsed + coverAmount;
398✔
484

485
    // surge price is applied for the capacity used above SURGE_THRESHOLD_RATIO.
486
    // the surge price starts at zero and increases linearly.
487
    // to simplify things, we're working with fractions/ratios instead of percentages,
488
    // ie 0 to 1 instead of 0% to 100%, 100% = 1 (a unit).
489
    //
490
    // surgeThreshold = SURGE_THRESHOLD_RATIO / SURGE_THRESHOLD_DENOMINATOR
491
    //                = 90_00 / 100_00 = 0.9
492
    uint surgeStartPoint = totalCapacity * SURGE_THRESHOLD_RATIO / SURGE_THRESHOLD_DENOMINATOR;
398✔
493

494
    // Capacity and surge pricing
495
    //
496
    //        i        f                         s
497
    //   ▓▓▓▓▓░░░░░░░░░                          ▒▒▒▒▒▒▒▒▒▒
498
    //
499
    //  i - initial capacity used
500
    //  f - final capacity used
501
    //  s - surge start point
502

503
    // if surge does not apply just return base premium
504
    // i < f <= s case
505
    if (finalCapacityUsed <= surgeStartPoint) {
398✔
506
      return basePremium;
96✔
507
    }
508

509
    // calculate the premium amount incurred due to surge pricing
510
    uint amountOnSurge = finalCapacityUsed - surgeStartPoint;
302✔
511
    uint surgePremium = calculateSurgePremium(amountOnSurge, totalCapacity, allocationUnitsPerNxm);
302✔
512

513
    // if the capacity start point is before the surge start point
514
    // the surge premium starts at zero, so we just return it
515
    // i <= s < f case
516
    if (initialCapacityUsed <= surgeStartPoint) {
302!
517
      return basePremium + surgePremium;
302✔
518
    }
519

520
    // otherwise we need to subtract the part that was already used by other covers
521
    // s < i < f case
522
    uint amountOnSurgeSkipped = initialCapacityUsed - surgeStartPoint;
×
523
    uint surgePremiumSkipped = calculateSurgePremium(amountOnSurgeSkipped, totalCapacity, allocationUnitsPerNxm);
×
524

525
    return basePremium + surgePremium - surgePremiumSkipped;
×
526
  }
527

528
  // Calculates the premium for a given cover amount starting with the surge point
529
  function calculateSurgePremium(
530
    uint amountOnSurge,
531
    uint totalCapacity,
532
    uint allocationUnitsPerNxm
533
  ) public pure returns (uint) {
534

535
    // for every percent of capacity used, the surge price has a +2% increase per annum
536
    // meaning a +200% increase for 100%, ie x2 for a whole unit (100%) of capacity in ratio terms
537
    //
538
    // coverToCapacityRatio = amountOnSurge / totalCapacity
539
    // surgePriceStart = 0
540
    // surgePriceEnd = SURGE_PRICE_RATIO * coverToCapacityRatio
541
    //
542
    // surgePremium = amountOnSurge * surgePriceEnd / 2
543
    //              = amountOnSurge * SURGE_PRICE_RATIO * coverToCapacityRatio / 2
544
    //              = amountOnSurge * SURGE_PRICE_RATIO * amountOnSurge / totalCapacity / 2
545

546
    uint surgePremium = amountOnSurge * SURGE_PRICE_RATIO * amountOnSurge / totalCapacity / 2;
302✔
547

548
    // amountOnSurge has two decimals
549
    // dividing by ALLOCATION_UNITS_PER_NXM (=100) to normalize the result
550
    return surgePremium / allocationUnitsPerNxm;
302✔
551
  }
552

553
  /* ========== STAKING POOL CREATION ========== */
554

555
  function stakingPool(uint poolId) public view returns (IStakingPool) {
556
    return IStakingPool(
161✔
557
      StakingPoolLibrary.getAddress(address(stakingPoolFactory), poolId)
558
    );
559
  }
560

561
  function getStakingPoolCount() external view returns (uint) {
562
    return IStakingPoolFactory(stakingPoolFactory).stakingPoolCount();
3✔
563
  }
564

565
  function createStakingPool(
566
    bool isPrivatePool,
567
    uint initialPoolFee,
568
    uint maxPoolFee,
569
    ProductInitializationParams[] memory productInitParams,
570
    string calldata ipfsDescriptionHash
571
  ) external whenNotPaused onlyMember returns (uint /*poolId*/, address /*stakingPoolAddress*/) {
572

573
    ICoverProducts _coverProducts = coverProducts();
18✔
574

575
    ProductInitializationParams[] memory initializedProducts = _coverProducts.prepareStakingProductsParams(
18✔
576
      productInitParams
577
    );
578

579
    (uint poolId, address stakingPoolAddress) = ICompleteStakingPoolFactory(stakingPoolFactory).create(coverContract);
18✔
580

581
    IStakingPool(stakingPoolAddress).initialize(
18✔
582
      isPrivatePool,
583
      initialPoolFee,
584
      maxPoolFee,
585
      poolId,
586
      ipfsDescriptionHash
587
    );
588

589
    tokenController().assignStakingPoolManager(poolId, msg.sender);
18✔
590

591
    _setInitialProducts(poolId, initializedProducts);
18✔
592

593
    return (poolId, stakingPoolAddress);
17✔
594
  }
595

596
  function _setInitialProducts(uint poolId, ProductInitializationParams[] memory params) internal {
597

598
    uint globalMinPriceRatio = cover().getGlobalMinPriceRatio();
26✔
599
    uint totalTargetWeight;
26✔
600

601
    for (uint i = 0; i < params.length; i++) {
26✔
602

603
      ProductInitializationParams memory param = params[i];
1,079✔
604

605
      if (params[i].targetPrice < globalMinPriceRatio) {
1,079✔
606
        revert TargetPriceBelowGlobalMinPriceRatio();
1✔
607
      }
608

609
      if (param.targetPrice > TARGET_PRICE_DENOMINATOR) {
1,078✔
610
        revert TargetPriceTooHigh();
1✔
611
      }
612

613
      if (param.weight > WEIGHT_DENOMINATOR) {
1,077✔
614
        revert TargetWeightTooHigh();
1✔
615
      }
616

617
      StakedProduct memory product;
1,076✔
618
      product.bumpedPrice = param.initialPrice;
1,076✔
619
      product.bumpedPriceUpdateTime = block.timestamp.toUint32();
1,076✔
620
      product.targetPrice = param.targetPrice;
1,076✔
621
      product.targetWeight = param.weight;
1,076✔
622
      product.lastEffectiveWeight = param.weight;
1,076✔
623

624
      // sstore
625
      _products[poolId][param.productId] = product;
1,076✔
626

627
      totalTargetWeight += param.weight;
1,076✔
628
    }
629

630
    if (totalTargetWeight > MAX_TOTAL_WEIGHT) {
23✔
631
      revert TotalTargetWeightExceeded();
1✔
632
    }
633

634
    weights[poolId] = Weights({
22✔
635
      totalTargetWeight: totalTargetWeight.toUint32(),
636
      totalEffectiveWeight: totalTargetWeight.toUint32()
637
    });
638
  }
639

640
  // future role transfers
641
  function changeStakingPoolFactoryOperator(address _operator) external onlyInternal {
642
    ICompleteStakingPoolFactory(stakingPoolFactory).changeOperator(_operator);
1✔
643
  }
644

645
  /* dependencies */
646

647
  function tokenController() internal view returns (ITokenController) {
648
    return ITokenController(internalContracts[uint(ID.TC)]);
18✔
649
  }
650

651
  function coverProducts() internal view returns (ICoverProducts) {
652
    return ICoverProducts(internalContracts[uint(ID.CP)]);
150✔
653
  }
654

655
  function cover() internal view returns (ICover) {
656
    return ICover(coverContract);
43✔
657
  }
658

659
  function changeDependentContractAddress() external {
660
    internalContracts[uint(ID.MR)] = master.getLatestAddress("MR");
16✔
661
    internalContracts[uint(ID.TC)] = master.getLatestAddress("TC");
16✔
662
    internalContracts[uint(ID.CP)] = master.getLatestAddress("CP");
16✔
663
  }
664

665
}
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