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

NexusMutual / smart-contracts / e3e758b8-a14e-43c1-bfa2-eb047e041d32

pending completion
e3e758b8-a14e-43c1-bfa2-eb047e041d32

Pull #691

circleci

shark0der
Pass staking pool factory instead of staking pool implementation to StakingProduct as constructor arg
Pull Request #691: Migration: LegacyPooledStaking changes & asserts in migration.js & scripts

850 of 1216 branches covered (69.9%)

Branch coverage included in aggregate %.

13 of 13 new or added lines in 5 files covered. (100.0%)

2397 of 2929 relevant lines covered (81.84%)

97.0 hits per line

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

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

3
pragma solidity ^0.8.9;
4

5
import "../../abstract/Multicall.sol";
6
import "../../interfaces/ICover.sol";
7
import "../../interfaces/INXMMaster.sol";
8
import "../../interfaces/IStakingNFT.sol";
9
import "../../interfaces/IStakingPool.sol";
10
import "../../interfaces/IStakingProducts.sol";
11
import "../../interfaces/IStakingPoolFactory.sol";
12
import "../../libraries/StakingPoolLibrary.sol";
13
import "../../libraries/UncheckedMath.sol";
14

15
contract StakingViewer is Multicall {
16
  using UncheckedMath for uint;
17

18
  struct Pool {
19
    uint poolId;
20
    bool isPrivatePool;
21
    address manager;
22
    uint poolFee;
23
    uint maxPoolFee;
24
    uint activeStake;
25
    uint currentAPY;
26
  }
27

28
  struct StakingProduct {
29
    uint productId;
30
    uint lastEffectiveWeight;
31
    uint targetWeight;
32
    uint targetPrice;
33
    uint bumpedPrice;
34
  }
35

36
  struct Deposit {
37
    uint tokenId;
38
    uint trancheId;
39
    uint stake;
40
    uint stakeShares;
41
    uint reward;
42
  }
43

44
  struct Token {
45
    uint tokenId;
46
    uint poolId;
47
    uint activeStake;
48
    uint expiredStake;
49
    uint rewards;
50
    Deposit[] deposits;
51
  }
52

53
  struct TokenPoolMap {
54
    uint poolId;
55
    uint tokenId;
56
  }
57

58
  struct AggregatedTokens {
59
    uint totalActiveStake;
60
    uint totalExpiredStake;
61
    uint totalRewards;
62
  }
63

64
  struct AggregatedRewards {
65
    uint totalRewards;
66
    uint[] trancheIds;
67
  }
68

69
  INXMMaster public immutable master;
70
  IStakingNFT public immutable stakingNFT;
71
  IStakingPoolFactory public immutable stakingPoolFactory;
72
  IStakingProducts public immutable stakingProducts;
73

74
  uint public constant TRANCHE_DURATION = 91 days;
75
  uint public constant MAX_ACTIVE_TRANCHES = 8;
76
  uint public constant ONE_NXM = 1 ether;
77
  uint public constant TRANCHE_ID_AT_DEPLOY = 213; // first active tranche at deploy time
78
  uint public constant MAX_UINT = type(uint).max;
79

80
  constructor(
81
    INXMMaster _master,
82
    IStakingNFT _stakingNFT,
83
    IStakingPoolFactory _stakingPoolFactory,
84
    IStakingProducts _stakingProducts
85
  ) {
86
    master = _master;
87
    stakingNFT = _stakingNFT;
88
    stakingPoolFactory = _stakingPoolFactory;
89
    stakingProducts = _stakingProducts;
90
  }
91

92
  function cover() internal view returns (ICover) {
93
    return ICover(master.contractAddresses('CO'));
94
  }
95

96
  function stakingPool(uint poolId) public view returns (IStakingPool) {
97
    return IStakingPool(
98
      StakingPoolLibrary.getAddress(address(stakingPoolFactory), poolId)
99
    );
100
  }
101

102
  /* ========== STAKING POOL ========== */
103

104
  function getPool(uint poolId) public view returns (Pool memory pool) {
105

106
    IStakingPool _stakingPool = stakingPool(poolId);
107

108
    pool.poolId = poolId;
109
    pool.isPrivatePool = _stakingPool.isPrivatePool();
110
    pool.manager = _stakingPool.manager();
111
    pool.poolFee = _stakingPool.getPoolFee();
112
    pool.maxPoolFee = _stakingPool.getMaxPoolFee();
113
    pool.activeStake = _stakingPool.getActiveStake();
114
    pool.currentAPY =
115
      _stakingPool.getActiveStake() != 0
116
        ? _stakingPool.getRewardPerSecond() * 365 days / _stakingPool.getActiveStake()
117
        : 0;
118

119
    return pool;
120
  }
121

122
  function getPools(uint[] memory poolIds) public view returns (Pool[] memory pools) {
123

124
    uint poolsLength = poolIds.length;
125
    pools = new Pool[](poolsLength);
126

127
    for (uint i = 0; i < poolsLength; i++) {
128
      pools[i] = getPool(poolIds[i]);
129
    }
130

131
    return pools;
132
  }
133

134
  function getAllPools() public view returns (Pool[] memory pools) {
135

136
    uint poolCount = stakingPoolFactory.stakingPoolCount();
137
    pools = new Pool[](poolCount);
138

139
    for (uint i = 0; i < poolCount; i++) {
140
      pools[i] = getPool(i+1); // poolId starts from 1
141
    }
142

143
    return pools;
144
  }
145

146
  function getProductPools(uint productId) public view returns (Pool[] memory pools) {
147
    uint queueSize = 0;
148
    uint poolCount = stakingPoolFactory.stakingPoolCount();
149
    Pool[] memory stakedPoolsQueue = new Pool[](poolCount);
150

151
    for (uint i = 1; i <= poolCount; i++) {
152
      (
153
        uint lastEffectiveWeight,
154
        uint targetWeight,
155
        /* uint targetPrice */,
156
        /* uint bumpedPrice */,
157
        uint bumpedPriceUpdateTime
158
      ) = stakingProducts.getProduct(i, productId);
159

160
      if (targetWeight == 0 && lastEffectiveWeight == 0 && bumpedPriceUpdateTime == 0) {
×
161
        continue;
162
      }
163

164
      Pool memory pool = getPool(i);
165
      stakedPoolsQueue[queueSize] = pool;
166
      queueSize++;
167
    }
168
    pools = new Pool[](queueSize);
169

170
    for (uint i = 0; i < queueSize; i++) {
171
      pools[i] = stakedPoolsQueue[i];
172
    }
173

174
    return pools;
175
  }
176

177
  /* ========== PRODUCTS ========== */
178

179
  function getPoolProducts(uint poolId) public view returns (StakingProduct[] memory products) {
180

181
    uint stakedProductsCount = 0;
182
    uint coverProductCount = cover().productsCount();
183
    StakingProduct[] memory stakedProductsQueue = new StakingProduct[](coverProductCount);
184

185
    for (uint i = 0; i < coverProductCount; i++) {
186
      (
187
        uint lastEffectiveWeight,
188
        uint targetWeight,
189
        uint targetPrice,
190
        uint bumpedPrice,
191
        uint bumpedPriceUpdateTime
192
      ) = stakingProducts.getProduct(poolId, i);
193

194
      if (targetWeight == 0 && lastEffectiveWeight == 0 && bumpedPriceUpdateTime == 0) {
×
195
        continue;
196
      }
197

198
      StakingProduct memory product;
199
      product.productId = i;
200
      product.lastEffectiveWeight = lastEffectiveWeight;
201
      product.targetWeight = targetWeight;
202
      product.bumpedPrice = bumpedPrice;
203
      product.targetPrice = targetPrice;
204

205
      stakedProductsQueue[stakedProductsCount] = product;
206
      stakedProductsCount++;
207
    }
208

209
    products = new StakingProduct[](stakedProductsCount);
210

211
    for (uint i = 0; i < stakedProductsCount; i++) {
212
      products[i] = stakedProductsQueue[i];
213
    }
214

215
    return products;
216
  }
217

218
  /* ========== TOKENS AND DEPOSITS ========== */
219

220
  function getStakingPoolsOf(
221
    uint[] memory tokenIds
222
  ) public view returns (TokenPoolMap[] memory tokenPools) {
223

224
    tokenPools = new TokenPoolMap[](tokenIds.length);
225

226
    for (uint i = 0; i < tokenIds.length; i++) {
227
      uint tokenId = tokenIds[i];
228
      uint poolId = stakingNFT.stakingPoolOf(tokenId);
229
      tokenPools[i] = TokenPoolMap(poolId, tokenId);
230
    }
231

232
    return tokenPools;
233
  }
234

235
  function _getToken(uint poolId, uint tokenId) internal view returns (Token memory token) {
236

237
    IStakingPool _stakingPool = stakingPool(poolId);
238

239
    uint firstActiveTrancheId = block.timestamp / TRANCHE_DURATION;
240
    uint depositCount;
241

242
    Deposit[] memory depositsQueue;
243
    {
244
      uint maxTranches = firstActiveTrancheId - TRANCHE_ID_AT_DEPLOY + MAX_ACTIVE_TRANCHES;
245
      depositsQueue = new Deposit[](maxTranches);
246
    }
247

248
    // Active tranches
249

250
    for (uint i = 0; i < MAX_ACTIVE_TRANCHES; i++) {
251
      (
252
        uint lastAccNxmPerRewardShare,
253
        uint pendingRewards,
254
        uint stakeShares,
255
        uint rewardsShares
256
      ) = _stakingPool.getDeposit(tokenId, firstActiveTrancheId + i);
257

258
      if (rewardsShares == 0) {
×
259
        continue;
260
      }
261

262
      Deposit memory deposit;
263
      deposit.tokenId = tokenId;
264
      deposit.trancheId = firstActiveTrancheId + i;
265

266
      uint stake =
267
        _stakingPool.getActiveStake()
268
        * stakeShares
269
        / _stakingPool.getStakeSharesSupply();
270

271
      uint newRewardPerShare = _stakingPool.getAccNxmPerRewardsShare().uncheckedSub(lastAccNxmPerRewardShare);
272
      uint reward = pendingRewards + newRewardPerShare * rewardsShares / ONE_NXM;
273

274
      deposit.stake = stake;
275
      deposit.stakeShares = stakeShares;
276
      deposit.reward = reward;
277
      depositsQueue[depositCount++] = deposit;
278

279
      token.activeStake += stake;
280
      token.rewards += reward;
281
    }
282

283
    // Expired tranches
284

285
    for (uint i = TRANCHE_ID_AT_DEPLOY; i < firstActiveTrancheId; i++) {
286
      (
287
        uint lastAccNxmPerRewardShare,
288
        uint pendingRewards,
289
        uint stakeShares,
290
        uint rewardsShares
291
      ) = _stakingPool.getDeposit(tokenId, i);
292

293
      if (rewardsShares == 0) {
×
294
        continue;
295
      }
296

297
      (
298
        uint accNxmPerRewardShareAtExpiry,
299
        uint stakeAmountAtExpiry,
300
        uint stakeShareSupplyAtExpiry
301
      ) = _stakingPool.getExpiredTranche(i);
302

303
      // to avoid this the workaround is to call processExpirations as the first call in the
304
      // multicall batch. this will require the call to be explicitly be static in js:
305
      // viewer.callStatic.multicall(...)
306
      require(stakeShareSupplyAtExpiry != 0, "Tranche expired but expirations were not processed");
×
307

308
      Deposit memory deposit;
309
      deposit.stake = stakeAmountAtExpiry * stakeShares / stakeShareSupplyAtExpiry;
310
      deposit.stakeShares = stakeShares;
311

312
      uint newRewardPerShare = accNxmPerRewardShareAtExpiry.uncheckedSub(lastAccNxmPerRewardShare);
313
      deposit.reward = pendingRewards + newRewardPerShare * rewardsShares / ONE_NXM;
314

315
      deposit.tokenId = tokenId;
316
      deposit.trancheId = i;
317

318
      depositsQueue[depositCount] = deposit;
319
      depositCount++;
320

321
      token.expiredStake += deposit.stake;
322
      token.rewards += deposit.reward;
323
    }
324

325
    token.tokenId = tokenId;
326
    token.poolId = poolId;
327
    token.deposits = new Deposit[](depositCount);
328

329
    for (uint i = 0; i < depositCount; i++) {
330
      token.deposits[i] = depositsQueue[i];
331
    }
332

333
    return token;
334
  }
335

336
  function getToken(uint tokenId) public view returns (Token memory token) {
337
    uint poolId = stakingNFT.stakingPoolOf(tokenId);
338
    return _getToken(poolId, tokenId);
339
  }
340

341
  function getTokens(uint[] memory tokenIds) public view returns (Token[] memory tokens) {
342

343
    tokens = new Token[](tokenIds.length);
344

345
    for (uint i = 0; i < tokenIds.length; i++) {
346
      uint poolId = stakingNFT.stakingPoolOf(tokenIds[i]);
347
      tokens[i] = _getToken(poolId, tokenIds[i]);
348
    }
349

350
    return tokens;
351
  }
352

353
  function getAggregatedTokens(
354
    uint[] calldata tokenIds
355
  ) public view returns (AggregatedTokens memory aggregated) {
356

357
    for (uint i = 0; i < tokenIds.length; i++) {
358
      Token memory token = getToken(tokenIds[i]);
359
      aggregated.totalActiveStake += token.activeStake;
360
      aggregated.totalExpiredStake += token.expiredStake;
361
      aggregated.totalRewards += token.rewards;
362
    }
363

364
    return aggregated;
365
  }
366

367
  function getManagerRewards (uint[] memory poolIds) public view returns (Token[] memory tokens) {
368
    tokens = new Token[](poolIds.length);
369

370
    for (uint i = 0; i < poolIds.length; i++) {
371
      tokens[i] = _getToken(poolIds[i], 0);
372
    }
373
  }
374
}
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

© 2026 Coveralls, Inc