diff --git a/contracts/interfaces/IStakingPool.sol b/contracts/interfaces/IStakingPool.sol index 5aee7d00c8..42bc6cdf98 100644 --- a/contracts/interfaces/IStakingPool.sol +++ b/contracts/interfaces/IStakingPool.sol @@ -57,8 +57,7 @@ interface IStakingPool { bool isPrivatePool, uint initialPoolFee, uint maxPoolFee, - uint _poolId, - string memory ipfsDescriptionHash + uint _poolId ) external; function processExpirations(bool updateUntilCurrentTimestamp) external; @@ -164,8 +163,6 @@ interface IStakingPool { event PoolFeeChanged(address indexed manager, uint newFee); - event PoolDescriptionSet(string ipfsDescriptionHash); - event Withdraw(address indexed user, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn); event StakeBurned(uint amount); diff --git a/contracts/interfaces/IStakingProducts.sol b/contracts/interfaces/IStakingProducts.sol index bbec85a46d..d4be324fad 100644 --- a/contracts/interfaces/IStakingProducts.sol +++ b/contracts/interfaces/IStakingProducts.sol @@ -47,6 +47,8 @@ interface IStakingProducts { uint bumpedPriceUpdateTime ); + function getPoolManager(uint poolId) external view returns (address); + /* ============= PRICING FUNCTIONS ============= */ function getPremium( @@ -117,6 +119,12 @@ interface IStakingProducts { function changeStakingPoolFactoryOperator(address newOperator) external; + function setPoolMetadata(uint poolId, string memory ipfsHash) external; + + function getPoolMetadata(uint poolId) external returns (string memory ipfsHash); + + function setInitialMetadata(string[] calldata ipfsHashes) external; + /* ============= EVENTS ============= */ event ProductUpdated(uint productId, uint8 targetWeight, uint96 targetPrice); diff --git a/contracts/mocks/generic/StakingPoolGeneric.sol b/contracts/mocks/generic/StakingPoolGeneric.sol index 48b22d216e..7809420d8a 100644 --- a/contracts/mocks/generic/StakingPoolGeneric.sol +++ b/contracts/mocks/generic/StakingPoolGeneric.sol @@ -6,7 +6,7 @@ import "../../interfaces/IStakingPool.sol"; contract StakingPoolGeneric is IStakingPool { - function initialize(bool, uint, uint, uint, string memory) external virtual { + function initialize(bool, uint, uint, uint) external virtual { revert("Unsupported"); } diff --git a/contracts/mocks/generic/StakingProductsGeneric.sol b/contracts/mocks/generic/StakingProductsGeneric.sol index 04f792fbbc..e3bc7a6a60 100644 --- a/contracts/mocks/generic/StakingProductsGeneric.sol +++ b/contracts/mocks/generic/StakingProductsGeneric.sol @@ -28,6 +28,10 @@ contract StakingProductsGeneric is IStakingProducts { revert("Unsupported"); } + function getPoolManager(uint) public pure override returns (address) { + revert("Unsupported"); + } + function getPremium(uint, uint, uint, uint, uint, uint, uint, bool, uint, uint) public virtual returns (uint) { revert("Unsupported"); } @@ -36,7 +40,6 @@ contract StakingProductsGeneric is IStakingProducts { revert("Unsupported"); } - function calculatePremium(StakedProduct memory, uint, uint, uint, uint, uint, uint, uint, uint, uint) public virtual pure returns (uint, StakedProduct memory) { revert("Unsupported"); } @@ -65,4 +68,16 @@ contract StakingProductsGeneric is IStakingProducts { function changeStakingPoolFactoryOperator(address) external virtual pure { revert("Unsupported"); } + + function setPoolMetadata(uint, string memory) external pure { + revert("Unsupported"); + } + + function getPoolMetadata(uint) external pure returns (string memory) { + revert("Unsupported"); + } + + function setInitialMetadata(string[] calldata) external pure { + revert("Unsupported"); + } } diff --git a/contracts/mocks/modules/Cover/COMockStakingPool.sol b/contracts/mocks/modules/Cover/COMockStakingPool.sol index be465857c8..0b8246d4a6 100644 --- a/contracts/mocks/modules/Cover/COMockStakingPool.sol +++ b/contracts/mocks/modules/Cover/COMockStakingPool.sol @@ -54,14 +54,12 @@ contract COMockStakingPool is StakingPoolGeneric { bool _isPrivatePool, uint _initialPoolFee, uint _maxPoolFee, - uint _poolId, - string calldata _ipfsDescriptionHash /* ipfsDescriptionHash */ + uint _poolId ) external override { isPrivatePool = _isPrivatePool; poolFee = uint8(_initialPoolFee); maxPoolFee = uint8(_maxPoolFee); poolId = uint40(_poolId); - ipfsHash = _ipfsDescriptionHash; } function requestAllocation( diff --git a/contracts/mocks/modules/Cover/COMockStakingProducts.sol b/contracts/mocks/modules/Cover/COMockStakingProducts.sol index 313f646407..6b88638a07 100644 --- a/contracts/mocks/modules/Cover/COMockStakingProducts.sol +++ b/contracts/mocks/modules/Cover/COMockStakingProducts.sol @@ -70,7 +70,7 @@ contract COMockStakingProducts is StakingProductsGeneric { uint initialPoolFee, uint maxPoolFee, ProductInitializationParams[] memory productInitParams, - string calldata ipfsDescriptionHash + string calldata /*ipfsDescriptionHash*/ ) external override returns (uint /*poolId*/, address /*stakingPoolAddress*/) { uint numProducts = productInitParams.length; @@ -110,8 +110,7 @@ contract COMockStakingProducts is StakingProductsGeneric { isPrivatePool, initialPoolFee, maxPoolFee, - poolId, - ipfsDescriptionHash + poolId ); tokenController().assignStakingPoolManager(poolId, msg.sender); diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index bec2eb9111..2d6e3197c7 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -173,8 +173,7 @@ contract StakingPool is IStakingPool, Multicall { bool _isPrivatePool, uint _initialPoolFee, uint _maxPoolFee, - uint _poolId, - string calldata ipfsDescriptionHash + uint _poolId ) external { if (msg.sender != address(stakingProducts)) { @@ -193,8 +192,6 @@ contract StakingPool is IStakingPool, Multicall { poolFee = uint8(_initialPoolFee); maxPoolFee = uint8(_maxPoolFee); poolId = _poolId.toUint40(); - - emit PoolDescriptionSet(ipfsDescriptionHash); } // updateUntilCurrentTimestamp forces rewards update until current timestamp not just until @@ -1315,10 +1312,6 @@ contract StakingPool is IStakingPool, Multicall { emit PoolPrivacyChanged(msg.sender, _isPrivatePool); } - function setPoolDescription(string memory ipfsDescriptionHash) external onlyManager { - emit PoolDescriptionSet(ipfsDescriptionHash); - } - /* fixes */ function updateRewardsShares( diff --git a/contracts/modules/staking/StakingProducts.sol b/contracts/modules/staking/StakingProducts.sol index 0f47f41563..f3a2cd971e 100644 --- a/contracts/modules/staking/StakingProducts.sol +++ b/contracts/modules/staking/StakingProducts.sol @@ -15,15 +15,29 @@ import "../../libraries/StakingPoolLibrary.sol"; contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { using SafeUintCast for uint; + // pool id => product id => Product + mapping(uint => mapping(uint => StakedProduct)) private _products; + // pool id => { totalEffectiveWeight, totalTargetWeight } + mapping(uint => Weights) public weights; + + // pool id => metadata + mapping(uint => string) internal poolMetadata; + + address public immutable coverContract; + address public immutable stakingPoolFactory; + uint public constant SURGE_PRICE_RATIO = 2 ether; uint public constant SURGE_THRESHOLD_RATIO = 90_00; // 90.00% uint public constant SURGE_THRESHOLD_DENOMINATOR = 100_00; // 100.00% + // base price bump // +0.2% for each 1% of capacity used, ie +20% for 100% uint public constant PRICE_BUMP_RATIO = 20_00; // 20% + // bumped price smoothing // 0.5% per day uint public constant PRICE_CHANGE_PER_DAY = 200; // 2% + uint public constant INITIAL_PRICE_DENOMINATOR = 100_00; uint public constant TARGET_PRICE_DENOMINATOR = 100_00; uint public constant MAX_TOTAL_WEIGHT = 20_00; // 20x @@ -41,13 +55,12 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { uint public constant ALLOCATION_UNITS_PER_NXM = 100; uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM; - // pool id => product id => Product - mapping(uint => mapping(uint => StakedProduct)) private _products; - // pool id => { totalEffectiveWeight, totalTargetWeight } - mapping(uint => Weights) public weights; - - address public immutable coverContract; - address public immutable stakingPoolFactory; + modifier onlyManager(uint poolId) { + if (msg.sender != getPoolManager(poolId)) { + revert OnlyManager(); + } + _; + } constructor(address _coverContract, address _stakingPoolFactory) { coverContract = _coverContract; @@ -83,6 +96,14 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { ); } + function getPoolManager(uint poolId) public view override returns (address) { + return tokenController().getStakingPoolManager(poolId); + } + + function getPoolMetadata(uint poolId) external override view returns (string memory ipfsHash) { + return poolMetadata[poolId]; + } + function recalculateEffectiveWeights(uint poolId, uint[] calldata productIds) external { IStakingPool _stakingPool = stakingPool(poolId); @@ -165,7 +186,7 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { IStakingPool _stakingPool = stakingPool(poolId); - if (msg.sender != _stakingPool.manager()) { + if (msg.sender != tokenController().getStakingPoolManager(poolId)) { revert OnlyManager(); } @@ -567,29 +588,27 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { uint initialPoolFee, uint maxPoolFee, ProductInitializationParams[] memory productInitParams, - string calldata ipfsDescriptionHash - ) external whenNotPaused onlyMember returns (uint /*poolId*/, address /*stakingPoolAddress*/) { + string calldata ipfsHash + ) external override whenNotPaused onlyMember returns (uint /*poolId*/, address /*stakingPoolAddress*/) { ICoverProducts _coverProducts = coverProducts(); - ProductInitializationParams[] memory initializedProducts = _coverProducts.prepareStakingProductsParams( - productInitParams - ); - + // create and initialize staking pool (uint poolId, address stakingPoolAddress) = ICompleteStakingPoolFactory(stakingPoolFactory).create(coverContract); + IStakingPool(stakingPoolAddress).initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); - IStakingPool(stakingPoolAddress).initialize( - isPrivatePool, - initialPoolFee, - maxPoolFee, - poolId, - ipfsDescriptionHash - ); - + // assign pool manager tokenController().assignStakingPoolManager(poolId, msg.sender); + // set products + ProductInitializationParams[] memory initializedProducts = _coverProducts.prepareStakingProductsParams( + productInitParams + ); _setInitialProducts(poolId, initializedProducts); + // set metadata + poolMetadata[poolId] = ipfsHash; + return (poolId, stakingPoolAddress); } @@ -637,11 +656,31 @@ contract StakingProducts is IStakingProducts, MasterAwareV2, Multicall { }); } - // future role transfers + // future operator role transfers function changeStakingPoolFactoryOperator(address _operator) external onlyInternal { ICompleteStakingPoolFactory(stakingPoolFactory).changeOperator(_operator); } + function setPoolMetadata( + uint poolId, + string memory ipfsHash + ) external override onlyManager(poolId) { + poolMetadata[poolId] = ipfsHash; + } + + // temporary migration function + + function setInitialMetadata(string[] calldata ipfsHashes) external onlyAdvisoryBoard { + + uint poolCount = IStakingPoolFactory(stakingPoolFactory).stakingPoolCount(); + require(ipfsHashes.length == poolCount, "StakingProducts: Metadata length mismatch"); + require(bytes(poolMetadata[1]).length == 0, "StakingProducts: Metadata already set"); + + for (uint i = 0; i < poolCount; i++) { + poolMetadata[i + 1] = ipfsHashes[i]; + } + } + /* dependencies */ function tokenController() internal view returns (ITokenController) { diff --git a/test/unit/StakingPool/burnStake.js b/test/unit/StakingPool/burnStake.js index b94c3c1198..861d6c26a3 100644 --- a/test/unit/StakingPool/burnStake.js +++ b/test/unit/StakingPool/burnStake.js @@ -27,7 +27,6 @@ const poolInitParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [initialProduct], - ipfsDescriptionHash: 'Description Hash', }; const productTypeFixture = { @@ -77,7 +76,7 @@ async function burnStakeSetup() { const fixture = await loadFixture(setup); const { stakingPool, stakingProducts, coverProducts } = fixture; const [staker] = fixture.accounts.members; - const { poolId, initialPoolFee, maxPoolFee, products, ipfsDescriptionHash } = poolInitParams; + const { poolId, initialPoolFee, maxPoolFee, products } = poolInitParams; await coverProducts.setProductType(productTypeFixture, initialProduct.productId); await coverProducts.setProduct(coverProductTemplate, initialProduct.productId); @@ -87,7 +86,6 @@ async function burnStakeSetup() { initialPoolFee, maxPoolFee, poolId, - ipfsDescriptionHash, ); await stakingProducts.connect(fixture.stakingProductsSigner).setInitialProducts(poolId, products); diff --git a/test/unit/StakingPool/depositTo.js b/test/unit/StakingPool/depositTo.js index 79a98d8a12..988897b576 100644 --- a/test/unit/StakingPool/depositTo.js +++ b/test/unit/StakingPool/depositTo.js @@ -31,7 +31,6 @@ const poolInitParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [productParams], - ipfsDescriptionHash: 'Description Hash', }; const managerDepositId = 0; @@ -43,14 +42,13 @@ async function depositToSetup() { const fixture = await loadFixture(setup); const { stakingPool, stakingProducts, tokenController } = fixture; const { defaultSender: manager } = fixture.accounts; - const { poolId, initialPoolFee, maxPoolFee, products, ipfsDescriptionHash } = poolInitParams; + const { poolId, initialPoolFee, maxPoolFee, products } = poolInitParams; await stakingPool.connect(fixture.stakingProductsSigner).initialize( false, // isPrivatePool initialPoolFee, maxPoolFee, poolId, - ipfsDescriptionHash, ); await tokenController.setStakingPoolManager(poolId, manager.address); diff --git a/test/unit/StakingPool/extendDeposit.js b/test/unit/StakingPool/extendDeposit.js index 96d2ca3758..f6526f0bfd 100644 --- a/test/unit/StakingPool/extendDeposit.js +++ b/test/unit/StakingPool/extendDeposit.js @@ -27,7 +27,6 @@ const poolInitParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [productParams], - ipfsDescriptionHash: 'Description Hash', }; const depositNftId = 1; @@ -39,10 +38,8 @@ async function extendDepositSetup() { const [user] = fixture.accounts.members; const manager = fixture.accounts.defaultSender; - const { poolId, initialPoolFee, maxPoolFee, products, ipfsDescriptionHash } = poolInitParams; - await stakingPool - .connect(fixture.stakingProductsSigner) - .initialize(false, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + const { poolId, initialPoolFee, maxPoolFee, products } = poolInitParams; + await stakingPool.connect(fixture.stakingProductsSigner).initialize(false, initialPoolFee, maxPoolFee, poolId); await tokenController.setStakingPoolManager(poolId, manager.address); await stakingProducts.connect(fixture.stakingProductsSigner).setInitialProducts(poolId, products); diff --git a/test/unit/StakingPool/initialize.js b/test/unit/StakingPool/initialize.js index 96eb2310b5..98c4ad4882 100644 --- a/test/unit/StakingPool/initialize.js +++ b/test/unit/StakingPool/initialize.js @@ -15,23 +15,20 @@ const initializeParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [product0], - ipfsDescriptionHash: 'Description Hash', }; describe('initialize', function () { it('reverts if cover contract is not the caller', async function () { const fixture = await loadFixture(setup); const { stakingPool, stakingProductsSigner } = fixture; - const { poolId, initialPoolFee, maxPoolFee, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, isPrivatePool } = initializeParams; await expect( - stakingPool.initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash), + stakingPool.initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId), ).to.be.revertedWithCustomError(stakingPool, 'OnlyStakingProductsContract'); await expect( - stakingPool - .connect(stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash), + stakingPool.connect(stakingProductsSigner).initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId), ).to.not.be.reverted; }); @@ -39,35 +36,29 @@ describe('initialize', function () { const fixture = await loadFixture(setup); const { stakingPool, stakingProductsSigner } = fixture; - const { poolId, maxPoolFee, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, maxPoolFee, isPrivatePool } = initializeParams; await expect( - stakingPool - .connect(stakingProductsSigner) - .initialize(isPrivatePool, maxPoolFee + 1, maxPoolFee, poolId, ipfsDescriptionHash), + stakingPool.connect(stakingProductsSigner).initialize(isPrivatePool, maxPoolFee + 1, maxPoolFee, poolId), ).to.be.revertedWithCustomError(stakingPool, 'PoolFeeExceedsMax'); }); it('reverts if max pool fee is 100%', async function () { const fixture = await loadFixture(setup); const { stakingPool, stakingProductsSigner } = fixture; - const { poolId, initialPoolFee, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, isPrivatePool } = initializeParams; await expect( - stakingPool - .connect(stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, 100, poolId, ipfsDescriptionHash), + stakingPool.connect(stakingProductsSigner).initialize(isPrivatePool, initialPoolFee, 100, poolId), ).to.be.revertedWithCustomError(stakingPool, 'MaxPoolFeeAbove100'); }); it('correctly initialize pool parameters', async function () { const fixture = await loadFixture(setup); const { stakingPool, stakingProductsSigner } = fixture; - const { poolId, initialPoolFee, maxPoolFee, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, isPrivatePool } = initializeParams; - await stakingPool - .connect(stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + await stakingPool.connect(stakingProductsSigner).initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); expect(await stakingPool.getPoolFee()).to.be.equal(initialPoolFee); expect(await stakingPool.getMaxPoolFee()).to.be.equal(maxPoolFee); diff --git a/test/unit/StakingPool/processExpirations.js b/test/unit/StakingPool/processExpirations.js index b5683ff488..8a91a531ee 100644 --- a/test/unit/StakingPool/processExpirations.js +++ b/test/unit/StakingPool/processExpirations.js @@ -38,17 +38,14 @@ const poolInitParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [productParams], - ipfsDescriptionHash: 'Description Hash', }; async function proccessExpirationSetup() { const fixture = await loadFixture(setup); const { stakingPool, stakingProducts } = fixture; - const { poolId, initialPoolFee, maxPoolFee, products, ipfsDescriptionHash } = poolInitParams; + const { poolId, initialPoolFee, maxPoolFee, products } = poolInitParams; - await stakingPool - .connect(fixture.stakingProductsSigner) - .initialize(false, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + await stakingPool.connect(fixture.stakingProductsSigner).initialize(false, initialPoolFee, maxPoolFee, poolId); await stakingProducts.connect(fixture.stakingProductsSigner).setInitialProducts(poolId, products); diff --git a/test/unit/StakingPool/requestAllocation.js b/test/unit/StakingPool/requestAllocation.js index 65e977d85a..33af4222a3 100644 --- a/test/unit/StakingPool/requestAllocation.js +++ b/test/unit/StakingPool/requestAllocation.js @@ -120,13 +120,12 @@ async function requestAllocationSetup() { // Initialize staking pool const poolId = 1; const isPrivatePool = false; - const ipfsDescriptionHash = 'Staking pool 1'; const maxPoolFee = 10; // 10% const initialPoolFee = 7; // 7% await stakingPool .connect(fixture.stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); await stakingProducts .connect(fixture.stakingProductsSigner) diff --git a/test/unit/StakingPool/setPoolFee.js b/test/unit/StakingPool/setPoolFee.js index f63a0e7c3c..ab2e7b973a 100644 --- a/test/unit/StakingPool/setPoolFee.js +++ b/test/unit/StakingPool/setPoolFee.js @@ -38,18 +38,17 @@ const initializeParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [product], - ipfsDescriptionHash: 'Description Hash', }; async function setPoolFeeSetup() { const fixture = await loadFixture(setup); const { stakingPool, stakingProducts, tokenController } = fixture; - const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool } = initializeParams; const manager = fixture.accounts.defaultSender; await stakingPool .connect(fixture.stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); await tokenController.setStakingPoolManager(poolId, manager.address); await stakingProducts.connect(fixture.stakingProductsSigner).setInitialProducts(poolId, products); diff --git a/test/unit/StakingPool/setPoolPrivacy.js b/test/unit/StakingPool/setPoolPrivacy.js index 5c8f272f5e..1d38deec1d 100644 --- a/test/unit/StakingPool/setPoolPrivacy.js +++ b/test/unit/StakingPool/setPoolPrivacy.js @@ -15,7 +15,6 @@ const initializeParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [product0], - ipfsDescriptionHash: 'Descrition Hash', }; async function setPoolPrivacySetup() { @@ -23,11 +22,11 @@ async function setPoolPrivacySetup() { const { stakingPool, stakingProducts, tokenController } = fixture; const manager = fixture.accounts.defaultSender; - const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool } = initializeParams; await stakingPool .connect(fixture.stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); await tokenController.setStakingPoolManager(poolId, manager.address); await stakingProducts.connect(fixture.stakingProductsSigner).setInitialProducts(poolId, products); diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 4f557c43f3..1946a14837 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -31,7 +31,6 @@ const initializeParams = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% products: [product0], - ipfsDescriptionHash: 'Description Hash', }; const withdrawFixture = { @@ -47,11 +46,11 @@ async function withdrawSetup() { const { stakingPool, stakingProducts, tokenController } = fixture; const manager = fixture.accounts.defaultSender; - const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool, ipfsDescriptionHash } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, products, isPrivatePool } = initializeParams; await stakingPool .connect(fixture.stakingProductsSigner) - .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId, ipfsDescriptionHash); + .initialize(isPrivatePool, initialPoolFee, maxPoolFee, poolId); await tokenController.setStakingPoolManager(poolId, manager.address); diff --git a/test/unit/StakingPoolFactory/setup.js b/test/unit/StakingPoolFactory/setup.js index 89c4cc418f..966cf76837 100644 --- a/test/unit/StakingPoolFactory/setup.js +++ b/test/unit/StakingPoolFactory/setup.js @@ -1,9 +1,9 @@ const { ethers } = require('hardhat'); -const { getAccounts } = require('../../utils/accounts'); +const { getAccounts } = require('../utils').accounts; async function setup() { const accounts = await getAccounts(); - const operator = accounts.nonMembers[0]; + const [operator] = accounts.nonMembers; const stakingPoolFactory = await ethers.deployContract('StakingPoolFactory', [operator.address]); diff --git a/test/unit/StakingProducts/createStakingPool.js b/test/unit/StakingProducts/createStakingPool.js index 033464ca95..5c11c2be61 100644 --- a/test/unit/StakingProducts/createStakingPool.js +++ b/test/unit/StakingProducts/createStakingPool.js @@ -4,21 +4,23 @@ const { expect } = require('chai'); const { keccak256 } = require('ethereum-cryptography/keccak'); const { bytesToHex, hexToBytes } = require('ethereum-cryptography/utils'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + const setup = require('./setup'); + const { AddressZero } = ethers.constants; +const product = { + productId: 200, + weight: 100, + initialPrice: '500', + targetPrice: '500', +}; + const newPoolFixture = { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% - productInitializationParams: [ - { - productId: 200, - weight: 100, - initialPrice: '500', - targetPrice: '500', - }, - ], - ipfsDescriptionHash: 'Description Hash', + productInitializationParams: [product], + ipfsDescriptionHash: 'staking-pool-ipfs-metadata', }; async function createStakingPoolSetup() { @@ -34,18 +36,10 @@ async function createStakingPoolSetup() { }; const productId = initialProducts.length; + const productParam = { ...coverProductTemplate, initialPriceRatio: coverProductTemplate.initialPriceRatio }; - await coverProducts.setProduct( - { ...coverProductTemplate, initialPriceRatio: coverProductTemplate.initialPriceRatio }, - productId, - ); - await coverProducts.setProductType( - { - claimMethod: 1, - gracePeriod: 7 * 24 * 3600, // 7 days - }, - productId, - ); + await coverProducts.setProduct(productParam, productId); + await coverProducts.setProductType({ claimMethod: 1, gracePeriod: 7 * 24 * 3600 /* = 7 days */ }, productId); return fixture; } @@ -66,7 +60,7 @@ describe('createStakingPool', function () { initialPoolFee, maxPoolFee, productInitializationParams, - '', // ipfsDescriptionHash + 'staking-pool-ipfs-metadata', ), ).to.be.revertedWith('System is paused'); }); diff --git a/test/unit/StakingProducts/helpers.js b/test/unit/StakingProducts/helpers.js index 88d85963fb..97dfaa494e 100644 --- a/test/unit/StakingProducts/helpers.js +++ b/test/unit/StakingProducts/helpers.js @@ -1,7 +1,8 @@ const { expect } = require('chai'); const { ethers } = require('hardhat'); -const { getCurrentTrancheId } = require('../StakingPool/helpers'); -const { setEtherBalance } = require('../../utils/evm'); + +const { setEtherBalance } = require('../utils').evm; + const { BigNumber } = ethers; const { parseEther } = ethers.utils; const { AddressZero } = ethers.constants; @@ -57,6 +58,13 @@ const burnStakeParams = { deallocationAmount: 0, }; +const TRANCHE_DURATION = daysToSeconds(91); + +async function getCurrentTrancheId() { + const { timestamp } = await ethers.provider.getBlock('latest'); + return Math.floor(timestamp / TRANCHE_DURATION); +} + async function verifyProduct(params) { const { coverProducts } = this; let { product, productParams } = params; diff --git a/test/unit/StakingProducts/recalculateEffectiveWeight.js b/test/unit/StakingProducts/recalculateEffectiveWeight.js index 5390013384..cdb7c28f5f 100644 --- a/test/unit/StakingProducts/recalculateEffectiveWeight.js +++ b/test/unit/StakingProducts/recalculateEffectiveWeight.js @@ -1,7 +1,6 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { parseEther } = ethers.utils; -const { Zero, One } = ethers.constants; +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { allocateCapacity, depositTo, @@ -11,14 +10,17 @@ const { burnStakeParams, newProductTemplate, } = require('./helpers'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const setup = require('./setup'); -const { increaseTime, setEtherBalance } = require('../../utils').evm; +const { increaseTime, setEtherBalance } = require('../utils').evm; + +const { parseEther } = ethers.utils; +const { Zero, One } = ethers.constants; const DEFAULT_PRODUCTS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; const MAX_TARGET_WEIGHT = 100; const MAX_TOTAL_EFFECTIVE_WEIGHT = 2000; const UINT16_MAX = 65535; + describe('recalculateEffectiveWeight', function () { it('recalculating effective weight should have no effect for products not found in stakingPool', async function () { const fixture = await loadFixture(setup); diff --git a/test/unit/StakingProducts/setPoolMetadata.js b/test/unit/StakingProducts/setPoolMetadata.js new file mode 100644 index 0000000000..82722b59b5 --- /dev/null +++ b/test/unit/StakingProducts/setPoolMetadata.js @@ -0,0 +1,36 @@ +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const setup = require('./setup'); + +describe('setPoolMetadata', function () { + it('reverts if manager is not the caller', async function () { + const fixture = await loadFixture(setup); + const [nonManager] = fixture.accounts.nonMembers; + const stakingProducts = fixture.stakingProducts.connect(nonManager); + + const poolId = 1; + const ipfsHash = 'some-string'; + + await expect(stakingProducts.setPoolMetadata(poolId, ipfsHash)).to.be.revertedWithCustomError( + stakingProducts, + 'OnlyManager', + ); + }); + + it('updates pool metadata', async function () { + const fixture = await loadFixture(setup); + const [manager] = fixture.accounts.members; + const stakingProducts = fixture.stakingProducts.connect(manager); + + const poolId = 1; + const ipfsHash = 'some-string'; + + const initialMetadata = await stakingProducts.getPoolMetadata(poolId); + await stakingProducts.setPoolMetadata(poolId, ipfsHash); + const updatedMetadata = await stakingProducts.getPoolMetadata(poolId); + + expect(updatedMetadata).to.be.not.equal(initialMetadata); + expect(updatedMetadata).to.be.equal(ipfsHash); + }); +}); diff --git a/test/unit/StakingProducts/setProducts.js b/test/unit/StakingProducts/setProducts.js index 02689039ad..f30df705b5 100644 --- a/test/unit/StakingProducts/setProducts.js +++ b/test/unit/StakingProducts/setProducts.js @@ -1,12 +1,14 @@ const { expect } = require('chai'); const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + const { verifyProduct, depositTo, daysToSeconds, newProductTemplate } = require('./helpers'); +const { increaseTime, setEtherBalance } = require('../utils').evm; +const setup = require('./setup'); + const { AddressZero } = ethers.constants; const { parseEther } = ethers.utils; const { BigNumber } = ethers; -const { increaseTime, setEtherBalance } = require('../../utils/evm'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const setup = require('./setup'); const poolId = 1; @@ -50,7 +52,10 @@ describe('setProducts unit tests', function () { const [manager] = fixture.accounts.members; const product = { ...newProductTemplate }; - await expect(stakingProducts.connect(manager).setProducts(324985304958, [product])).to.be.revertedWithoutReason(); + await expect(stakingProducts.connect(manager).setProducts(324985304958, [product])).to.be.revertedWithCustomError( + stakingProducts, + 'OnlyManager', + ); }); it('should set products and store values correctly', async function () { diff --git a/test/unit/StakingProducts/setup.js b/test/unit/StakingProducts/setup.js index c508e4134c..1a90e2c126 100644 --- a/test/unit/StakingProducts/setup.js +++ b/test/unit/StakingProducts/setup.js @@ -1,12 +1,11 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { getAccounts } = require('../../utils/accounts'); +const { getAccounts } = require('../utils').accounts; const { setEtherBalance } = require('../utils').evm; const { Role } = require('../utils').constants; const { hex } = require('../utils').helpers; -const { BigNumber } = ethers; const { parseEther, getContractAddress } = ethers.utils; const { AddressZero } = ethers.constants; @@ -119,28 +118,32 @@ async function setup() { await master.enrollInternal(contract.address); } - let i = 0; + // setup a staking pool + const [member] = accounts.members; + + const [poolId, stakingPoolAddress] = await stakingProducts + .connect(member) + .callStatic.createStakingPool(false, 5, 5, [], 'ipfs hash'); + + await stakingProducts.connect(member).createStakingPool(false, 5, 5, [], 'ipfs hash'); + await tokenController.setStakingPoolManager(poolId, member.address); + + const stakingPool = await ethers.getContractAt('StakingPool', stakingPoolAddress); + + // set initial products const initialProducts = Array(200) .fill('') - .map(() => ({ ...initialProductTemplate, productId: i++ })); + .map((_, productId) => ({ ...initialProductTemplate, productId })); + // Add products to cover contract await Promise.all( initialProducts.map(async ({ productId, initialPrice: initialPriceRatio }) => { await coverProducts.setProduct({ ...coverProductTemplate, initialPriceRatio }, productId); await coverProducts.setProductType(ProductTypeFixture, productId); - await coverProducts.setPoolAllowed(productId, 1 /* poolID */, true); + await coverProducts.setPoolAllowed(productId, poolId, true); }), ); - const ret = await stakingProducts - .connect(accounts.members[0]) - .callStatic.createStakingPool(false, 5, 5, [], 'ipfs hash'); - - await stakingProducts.connect(accounts.members[0]).createStakingPool(false, 5, 5, [], 'ipfs hash'); - - const stakingPool = await ethers.getContractAt('StakingPool', ret[1]); - tokenController.setStakingPoolManager(1 /* poolID */, accounts.members[0].address); - const config = { PRICE_CHANGE_PER_DAY: await stakingProducts.PRICE_CHANGE_PER_DAY(), PRICE_BUMP_RATIO: await stakingProducts.PRICE_BUMP_RATIO(), @@ -165,8 +168,6 @@ async function setup() { const coverSigner = await ethers.getImpersonatedSigner(cover.address); await setEtherBalance(coverSigner.address, ethers.utils.parseEther('1')); - const poolId = BigNumber.from(await stakingPool.getPoolId()); - return { accounts, coverSigner,