Skip to content

Commit

Permalink
Merge pull request #805 from NexusMutual/fix/create-staking-pool-prod…
Browse files Browse the repository at this point in the history
…uct-check

Fix: Revert if trying to initialize new pool with forbidden or non-existing products
  • Loading branch information
shark0der committed May 4, 2023
2 parents 7474bcc + 0895332 commit 81b8a15
Show file tree
Hide file tree
Showing 18 changed files with 531 additions and 123 deletions.
3 changes: 2 additions & 1 deletion contracts/interfaces/ICover.sol
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ interface ICover {
) external returns (uint poolId, address stakingPoolAddress);

function isPoolAllowed(uint productId, uint poolId) external returns (bool);
function requirePoolIsAllowed(uint[] calldata productIds, uint poolId) external view;

/* ========== EVENTS ========== */

Expand All @@ -229,9 +230,9 @@ interface ICover {
error ProductDoesntExist();
error ProductTypeNotFound();
error ProductDeprecated();
error ProductDeprecatedOrNotInitialized();
error InvalidProductType();
error UnexpectedProductId();
error PoolNotAllowedForThisProduct(uint productId);

// Cover and payment assets
error CoverAssetNotSupported();
Expand Down
1 change: 0 additions & 1 deletion contracts/interfaces/IStakingProducts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ interface IStakingProducts {
error OnlyManager();

// Products & weights
error PoolNotAllowedForThisProduct();
error MustSetPriceForNewProducts();
error MustSetWeightForNewProducts();
error TargetPriceTooHigh();
Expand Down
3 changes: 2 additions & 1 deletion contracts/mocks/StakingPool/SPMockStakingProducts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.18;
import "../../abstract/MasterAwareV2.sol";
import "../../abstract/Multicall.sol";
import "../../interfaces/IStakingProducts.sol";
import "../../interfaces/ICover.sol";
import "../../libraries/Math.sol";
import "../../libraries/SafeUintCast.sol";
import "../../libraries/StakingPoolLibrary.sol";
Expand Down Expand Up @@ -129,7 +130,7 @@ contract SPMockStakingProducts is IStakingProducts, MasterAwareV2, Multicall {
for (uint i = 0; i < numProducts; i++) {
productIds[i] = params[i].productId;
if (!ICover(coverContract).isPoolAllowed(params[i].productId, poolId)) {
revert PoolNotAllowedForThisProduct();
revert ICover.PoolNotAllowedForThisProduct(params[i].productId);
}
}
(
Expand Down
12 changes: 11 additions & 1 deletion contracts/mocks/StakingProducts/StakingProductsMockCover.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ contract StakingProductsMockCover {
IStakingPoolFactory public stakingPoolFactory;
address public stakingPoolImplementation;

error ProductDeprecatedOrNotInitialized();

constructor(
ICoverNFT _coverNFT,
IStakingNFT _stakingNFT,
Expand Down Expand Up @@ -212,5 +214,13 @@ contract StakingProductsMockCover {
_capacityReductionRatios[i] = uint(product.capacityReductionRatio);
}
}
error ProductDeprecatedOrNotInitialized();

function requirePoolIsAllowed(uint[] calldata productIds, uint poolId) external view {
for (uint i = 0; i < productIds.length; i++) {
uint productId = productIds[i];
if (!allowedPools[productId][poolId]) {
revert ICover.PoolNotAllowedForThisProduct(productId);
}
}
}
}
83 changes: 52 additions & 31 deletions contracts/modules/cover/Cover.sol
Original file line number Diff line number Diff line change
Expand Up @@ -499,29 +499,33 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
uint maxPoolFee,
ProductInitializationParams[] memory productInitParams,
string calldata ipfsDescriptionHash
) external whenNotPaused returns (uint /*poolId*/, address /*stakingPoolAddress*/) {
) external whenNotPaused onlyMember returns (uint /*poolId*/, address /*stakingPoolAddress*/) {

if (msg.sender != master.getLatestAddress("PS")) {
uint numProducts = productInitParams.length;

// TODO: replace this with onlyMember modifier after the v2 release
require(
IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
msg.sender,
uint(IMemberRoles.Role.Member)
),
"Caller is not a member"
);
// override with initial price and check if pool is allowed
for (uint i = 0; i < numProducts; i++) {

// override with initial price
for (uint i = 0; i < productInitParams.length; i++) {
if (productInitParams[i].targetPrice < GLOBAL_MIN_PRICE_RATIO) {
revert TargetPriceBelowGlobalMinPriceRatio();
}

uint productId = productInitParams[i].productId;
productInitParams[i].initialPrice = _products[productId].initialPriceRatio;
uint productId = productInitParams[i].productId;

if (productInitParams[i].targetPrice < GLOBAL_MIN_PRICE_RATIO) {
revert TargetPriceBelowGlobalMinPriceRatio();
}
// if there is a list of allowed pools for this product - this pool didn't exist yet so it's not in it
if (allowedPools[productId].length > 0) {
revert PoolNotAllowedForThisProduct(productId);
}

if (productId >= _products.length) {
revert ProductDoesntExist();
}

if (_products[productId].isDeprecated) {
revert ProductDeprecated();
}

productInitParams[i].initialPrice = _products[productId].initialPriceRatio;
}

(uint poolId, address stakingPoolAddress) = stakingPoolFactory.create(address(this));
Expand Down Expand Up @@ -714,6 +718,7 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
revert CapacityReductionRatioAbove100Percent();
}

// TODO: https://github.com/NexusMutual/smart-contracts/issues/859
if (product.useFixedPrice) {
uint productId = param.productId == type(uint256).max ? _products.length : param.productId;
allowedPools[productId] = param.allowedPools;
Expand All @@ -731,6 +736,7 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
if (param.productId >= _products.length) {
revert ProductDoesntExist();
}

Product storage newProductValue = _products[param.productId];
newProductValue.isDeprecated = product.isDeprecated;
newProductValue.coverAssets = product.coverAssets;
Expand Down Expand Up @@ -781,21 +787,32 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
return uint(activeCover[assetId].totalActiveCoverInAsset);
}

function isPoolAllowed(uint productId, uint poolId) external view returns (bool) {
// Returns true if the product exists and the pool is authorized to have the product
function isPoolAllowed(uint productId, uint poolId) public view returns (bool) {

uint poolCount = allowedPools[productId].length;
uint poolCount = allowedPools[productId].length;

if (poolCount == 0) {
return true;
}

for (uint i = 0; i < poolCount; i++) {
if (allowedPools[productId][i] == poolId) {
// If no pools are specified, every pool is allowed
if (poolCount == 0) {
return true;
}
}

return false;
for (uint i = 0; i < poolCount; i++) {
if (allowedPools[productId][i] == poolId) {
return true;
}
}

// Product has allow list and pool is not in it
return false;
}

function requirePoolIsAllowed(uint[] calldata productIds, uint poolId) external view {
for (uint i = 0; i < productIds.length; i++) {
if (!isPoolAllowed(productIds[i], poolId) ) {
revert PoolNotAllowedForThisProduct(productIds[i]);
}
}
}

function globalCapacityRatio() external pure returns (uint) {
Expand All @@ -806,7 +823,7 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
return GLOBAL_REWARDS_RATIO;
}

function getPriceAndCapacityRatios(uint[] calldata productIds) public view returns (
function getPriceAndCapacityRatios(uint[] calldata productIds) external view returns (
uint _globalCapacityRatio,
uint _globalMinPriceRatio,
uint[] memory _initialPrices,
Expand All @@ -818,10 +835,14 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon, ReentrancyGuard {
_initialPrices = new uint[](productIds.length);

for (uint i = 0; i < productIds.length; i++) {
Product memory product = _products[productIds[i]];
if (product.initialPriceRatio == 0) {
revert ProductDeprecatedOrNotInitialized();
uint productId = productIds[i];

if (productId >= _products.length) {
revert ProductDoesntExist();
}

Product memory product = _products[productId];

_initialPrices[i] = uint(product.initialPriceRatio);
_capacityReductionRatios[i] = uint(product.capacityReductionRatio);
}
Expand Down
8 changes: 5 additions & 3 deletions contracts/modules/staking/StakingProducts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,12 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall {

for (uint i = 0; i < numProducts; i++) {
productIds[i] = params[i].productId;
if (!ICover(coverContract).isPoolAllowed(params[i].productId, poolId)) {
revert PoolNotAllowedForThisProduct();
}
}

// reverts if poolId is not allowed for any of these products
ICover(coverContract).requirePoolIsAllowed(productIds, poolId);

// reverts if any of the products do not exist
(
globalCapacityRatio,
globalMinPriceRatio,
Expand Down
31 changes: 0 additions & 31 deletions test/fork/external.js

This file was deleted.

4 changes: 4 additions & 0 deletions test/fork/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
describe('fork tests', function () {
require('./recalculate-effective-weights-bug-fix');
require('./staked-product-allowed-pools');
require('./migrated-claims');
require('./recalculate-effective-weights');
require('./withdraw-switch-membership-restrictions');
});
4 changes: 2 additions & 2 deletions test/fork/recalculate-effective-weights.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { setEtherBalance } = require('../utils/evm');
const { parseEther, defaultAbiCoder, toUtf8Bytes } = ethers.utils;
const { BigNumber } = ethers;
const { daysToSeconds } = require('../../lib/helpers');
const { V2Addresses, UserAddress, submitGovernanceProposal, getProductsInPool } = require('./utils');
const { V2Addresses, UserAddress, submitGovernanceProposal, getActiveProductsInPool } = require('./utils');
const {
calculateBasePrice,
calculateBasePremium,
Expand Down Expand Up @@ -81,7 +81,7 @@ describe('recalculateEffectiveWeight', function () {
const period = daysToSeconds(45);
const commissionRatio = 0;

const productsInThisPool = await getProductsInPool.call(this, { poolId });
const productsInThisPool = await getActiveProductsInPool.call(this, { poolId });
// pick a random product
const randomProduct = productsInThisPool[Math.floor(Math.random() * (productsInThisPool.length - 1))];
console.log('buying cover for product: ', randomProduct.productId, 'in pool: ', poolId);
Expand Down
Loading

0 comments on commit 81b8a15

Please sign in to comment.