diff --git a/contracts/mocks/Cover/CoverMockStakingPool.sol b/contracts/mocks/Cover/CoverMockStakingPool.sol index 23ef52d396..cb77bed928 100644 --- a/contracts/mocks/Cover/CoverMockStakingPool.sol +++ b/contracts/mocks/Cover/CoverMockStakingPool.sol @@ -5,28 +5,36 @@ pragma solidity ^0.8.9; import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import "@openzeppelin/contracts-v4/token/ERC721/ERC721.sol"; import "../../modules/staking/StakingPool.sol"; -contract CoverMockStakingPool is StakingPool { + +contract CoverMockStakingPool is IStakingPool, ERC721 { /* immutables */ address public immutable memberRoles; mapping (uint => uint) public usedCapacity; mapping (uint => uint) public stakedAmount; - + // product id => Product + mapping(uint => Product) public products; mapping (uint => uint) public mockPrices; - uint public constant MAX_PRICE_RATIO = 1e20; + uint public constant MAX_PRICE_RATIO = 10_000; + uint constant REWARDS_DENOMINATOR = 10_000; + + uint public poolId; + // erc721 supply + uint public totalSupply; + address public manager; constructor ( address _nxm, address _coverContract, ITokenController _tokenController, address _memberRoles - ) - StakingPool("Nexus Mutual Staking Pool", "NMSPT", _nxm, _coverContract, _tokenController) + ) ERC721("Nexus Mutual Staking Pool", "NMSPT") { memberRoles = _memberRoles; } @@ -35,28 +43,60 @@ contract CoverMockStakingPool is StakingPool { return string(abi.encodePacked(super.name(), " ", Strings.toString(poolId))); } - function initialize(address _manager, uint _poolId) external /*override*/ { + function initialize( + address _manager, + bool _isPrivatePool, + uint _initialPoolFee, + uint _maxPoolFee, + ProductInitializationParams[] calldata params, + uint _poolId + ) external { + _isPrivatePool; + _initialPoolFee; + _maxPoolFee; + params; + manager = _manager; _mint(_manager, totalSupply++); poolId = _poolId; } + function operatorTransferFrom(address from, address to, uint256 amount) external /*override*/ { require(msg.sender == memberRoles, "StakingPool: Caller is not MemberRoles"); _transfer(from, to, amount); } - function allocateCapacity( + + function allocateStake( + CoverRequest calldata request + ) external override returns (uint allocatedAmount, uint premium, uint rewardsInNXM) { + + usedCapacity[request.productId] += request.amount; + + uint premium = calculatePremium(mockPrices[request.productId], request.amount, request.period); + + return ( + request.amount, + premium, + premium * request.rewardRatio / REWARDS_DENOMINATOR + ); + } + + function deallocateStake( uint productId, - uint amountInNXM, + uint start, uint period, - uint rewardRatio, - uint initialPriceRatio - ) external /*override*/ returns (uint coveredAmountInNXM, uint premiumInNXM) { + uint amount, + uint premium, + uint globalRewardsRatio + ) external { + + // silence compiler warnings + productId; + start; period; - rewardRatio; - initialPriceRatio; - usedCapacity[productId] += amountInNXM; - return (amountInNXM, calculatePremium(mockPrices[productId], amountInNXM, period)); + amount; + premium; } function calculatePremium(uint priceRatio, uint coverAmount, uint period) public pure returns (uint) { @@ -67,15 +107,20 @@ contract CoverMockStakingPool is StakingPool { _mint(msg.sender, amount); } - function freeCapacity( - uint productId, - uint previousPeriod, - uint previousStartTime, - uint previousRewardAmount, - uint periodReduction, - uint coveredAmount - ) external /*override*/ { - // no-op + // used to transfer all nfts when a user switches the membership to a new address + function operatorTransfer( + address from, + address to, + uint[] calldata tokenIds + ) external { + uint length = tokenIds.length; + for (uint i = 0; i < length; i++) { + _safeTransfer(from, to, tokenIds[i], ""); + } + } + + function updateTranches() external { + revert("CoverMockStakingPool: not callable"); } function getAvailableCapacity(uint productId, uint capacityFactor) external /*override*/ view returns (uint) { @@ -102,7 +147,7 @@ contract CoverMockStakingPool is StakingPool { usedCapacity[productId] = amount; } - function setTargetPrice(uint productId, uint amount) external { + function setTargetPrice(uint productId, uint amount) external { products[productId].targetPrice = uint96(amount); } @@ -121,4 +166,71 @@ contract CoverMockStakingPool is StakingPool { function changeDependentContractAddress() external { // noop } + + function burnStake(uint productId, uint start, uint period, uint amount) external { + productId; + start; + period; + amount; + + // no-op + } + + function depositTo(DepositRequest[] memory requests) external returns (uint[] memory tokenIds) { + revert("CoverMockStakingPool: not callable"); + } + + function withdraw(WithdrawRequest[] memory params) external { + revert("CoverMockStakingPool: not callable"); + + } + + function addProducts(ProductParams[] memory params) external { + revert("CoverMockStakingPool: not callable"); + } + + function removeProducts(uint[] memory productIds) external { + revert("CoverMockStakingPool: not callable"); + } + + function setProductDetails(ProductParams[] memory params) external { + revert("CoverMockStakingPool: not callable"); + } + + function setPoolFee(uint newFee) external { + revert("CoverMockStakingPool: not callable"); + } + + function setPoolPrivacy(bool isPrivatePool) external { + revert("CoverMockStakingPool: not callable"); + } + + + function getActiveStake() external view returns (uint) { + revert("CoverMockStakingPool: not callable"); + } + + function getProductStake(uint productId, uint coverExpirationDate) external view returns (uint) { + revert("CoverMockStakingPool: not callable"); + } + + function getFreeProductStake(uint productId, uint coverExpirationDate) external view returns (uint) { + revert("CoverMockStakingPool: not callable"); + } + + function getAllocatedProductStake(uint productId) external view returns (uint) { + revert("CoverMockStakingPool: not callable"); + } + + function getPriceParameters( + uint productId, + uint maxCoverPeriod + ) external override view returns ( + uint activeCover, + uint[] memory staked, + uint lastBasePrice, + uint targetPrice + ) { + revert("CoverMockStakingPool: not callable"); + } } diff --git a/contracts/mocks/TokenControllerMock.sol b/contracts/mocks/TokenControllerMock.sol index fa43bd3ef6..7554f6f96b 100644 --- a/contracts/mocks/TokenControllerMock.sol +++ b/contracts/mocks/TokenControllerMock.sol @@ -7,10 +7,17 @@ import "../modules/token/NXMToken.sol"; contract TokenControllerMock is MasterAware { + struct StakingPoolNXMBalances { + uint128 rewards; + uint128 deposits; + } + NXMToken public token; address public addToWhitelistLastCalledWtih; address public removeFromWhitelistLastCalledWtih; + mapping(uint => StakingPoolNXMBalances) stakingPoolNXMBalances; + function mint(address _member, uint256 _amount) public onlyInternal { token.mint(_member, _amount); } @@ -39,7 +46,19 @@ contract TokenControllerMock is MasterAware { return true; } - /* unused functions */ + function mintStakingPoolNXMRewards(uint amount, uint poolId) external { + + mint(address(this), amount); + stakingPoolNXMBalances[poolId].rewards += uint128(amount); + } + + function burnStakingPoolNXMRewards(uint amount, uint poolId) external { + + burnFrom(address(this), amount); + stakingPoolNXMBalances[poolId].rewards -= uint128(amount); + } + + /* unused functions */ modifier unused { require(false, "Unexpected TokenControllerMock call"); diff --git a/contracts/modules/cover/Cover.sol b/contracts/modules/cover/Cover.sol index 0630b6ffa3..9e59b63dd1 100644 --- a/contracts/modules/cover/Cover.sol +++ b/contracts/modules/cover/Cover.sol @@ -35,6 +35,8 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon { uint public constant MAX_COVER_PERIOD = 364 days; uint private constant MIN_COVER_PERIOD = 28 days; + // this constant is used for calculating the normalized yearly percentage cost of cover + uint private constant ONE_YEAR = 365 days; uint private constant MAX_COMMISSION_RATIO = 2500; // 25% @@ -253,7 +255,7 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon { // priceRatio is normalized on a per year basis (eg. 1.5% per year) uint16 priceRatio = SafeUintCast.toUint16( divRound( - totalPremiumInNXM * PRICE_DENOMINATOR * MAX_COVER_PERIOD / params.period, + totalPremiumInNXM * PRICE_DENOMINATOR * ONE_YEAR / params.period, totalCoverAmountInNXM ) ); @@ -280,10 +282,6 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon { Product memory product = _products[params.productId]; uint gracePeriod = _productTypes[product.productType].gracePeriodInDays * 1 days; - if (true) { - // wrapped in if(true) to avoid the compiler warning about unreachable code - revert("capacity calculation: not implemented"); - } return _stakingPool.allocateStake( CoverRequest( @@ -418,7 +416,6 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon { ); } - // TODO: implement properly. we need the staking interface for burning. function performPayoutBurn( uint coverId, uint segmentId, @@ -533,24 +530,25 @@ contract Cover is ICover, MasterAwareV2, IStakingPoolBeacon { bool isPrivatePool, uint initialPoolFee, uint maxPoolFee, - ProductInitializationParams[] memory params, + ProductInitializationParams[] memory productInitializationParams, uint depositAmount, uint trancheId ) external returns (address stakingPoolAddress) { emit StakingPoolCreated(stakingPoolAddress, manager, stakingPoolImplementation); - // [todo] handle the creation of NFT 0 which is the default NFT owned by the pool manager + CoverUtilsLib.PoolInitializationParams memory poolInitializationParams = CoverUtilsLib.PoolInitializationParams( + stakingPoolCount++, + manager, + isPrivatePool, + initialPoolFee, + maxPoolFee + ); + return CoverUtilsLib.createStakingPool( _products, - CoverUtilsLib.PoolInitializationParams( - stakingPoolCount++, - manager, - isPrivatePool, - initialPoolFee, - maxPoolFee - ), - params, + poolInitializationParams, + productInitializationParams, depositAmount, trancheId, master.getLatestAddress("PS") diff --git a/contracts/modules/cover/CoverUtilsLib.sol b/contracts/modules/cover/CoverUtilsLib.sol index a9096f2a08..06d7475614 100644 --- a/contracts/modules/cover/CoverUtilsLib.sol +++ b/contracts/modules/cover/CoverUtilsLib.sol @@ -10,6 +10,7 @@ import "../../interfaces/ITokenController.sol"; import "../../libraries/SafeUintCast.sol"; import "./MinimalBeaconProxy.sol"; + library CoverUtilsLib { struct MigrateParams { @@ -124,7 +125,6 @@ library CoverUtilsLib { new MinimalBeaconProxy{ salt: bytes32(poolInitParams.poolId) }(address(this)) ); - if (msg.sender != pooledStakingAddress) { // override with initial price diff --git a/test/unit/Cover/buyCover.js b/test/unit/Cover/buyCover.js index 25d5f2bfb4..fe5e908dc3 100644 --- a/test/unit/Cover/buyCover.js +++ b/test/unit/Cover/buyCover.js @@ -1,13 +1,11 @@ const { assert, expect } = require('chai'); -const { - ethers: { - utils: { parseEther }, - }, -} = require('hardhat'); +const { ethers } = require('hardhat'); +const { utils: { parseEther } } = ethers; + const { constants: { ZERO_ADDRESS }, } = require('@openzeppelin/test-helpers'); -const { createStakingPool, assertCoverFields } = require('./helpers'); +const { createStakingPool, assertCoverFields, buyCoverOnOnePool, MAX_COVER_PERIOD } = require('./helpers'); const { bnEqual } = require('../utils').helpers; describe('buyCover', function () { @@ -23,7 +21,7 @@ describe('buyCover', function () { const productId = 0; const payoutAsset = 0; // ETH - const period = 3600 * 24 * 30; // 30 days + const period = 3600 * 24 * 364; // 30 days const amount = parseEther('1000'); @@ -34,7 +32,7 @@ describe('buyCover', function () { const capacityFactor = '10000'; - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); + await cover.connect(gv1).updateUintParameters([0], [capacityFactor]); await createStakingPool( cover, productId, capacity, targetPriceRatio, activeCover, stakingPoolManager, stakingPoolManager, targetPriceRatio, @@ -54,6 +52,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -83,7 +82,7 @@ describe('buyCover', function () { const productId = 0; const payoutAsset = 0; // ETH - const period = 3600 * 24 * 30; // 30 days + const period = 3600 * 24 * 28; // 30 days const amount = parseEther('1000'); @@ -94,7 +93,7 @@ describe('buyCover', function () { const capacityFactor = '10000'; - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); + await cover.connect(gv1).updateUintParameters([0], [capacityFactor]); await createStakingPool( cover, productId, capacity, targetPriceRatio, activeCover, stakingPoolManager, stakingPoolManager, targetPriceRatio, @@ -119,6 +118,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [ { poolId: '0', coverAmountInAsset: amount.div(2).toString() }, @@ -157,10 +157,6 @@ describe('buyCover', function () { const commissionRatio = '500'; // 5% - const capacityFactor = '10000'; - - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); - await createStakingPool( cover, productId, capacity, targetPriceRatio, activeCover, stakingPoolManager, stakingPoolManager, targetPriceRatio, ); @@ -188,6 +184,7 @@ describe('buyCover', function () { payWithNXM: true, commissionRatio: commissionRatio, commissionDestination: commissionReceiver.address, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -234,10 +231,6 @@ describe('buyCover', function () { const commissionRatio = '500'; // 5% - const capacityFactor = '10000'; - - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); - await createStakingPool( cover, productId, capacity, targetPriceRatio, activeCover, stakingPoolManager, stakingPoolManager, targetPriceRatio, ); @@ -265,6 +258,7 @@ describe('buyCover', function () { payWithNXM: false, commissionRatio: commissionRatio, commissionDestination: commissionReceiver.address, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -313,6 +307,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -347,6 +342,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -365,7 +361,7 @@ describe('buyCover', function () { const productId = 0; const payoutAsset = 0; // ETH - const period = 3600 * 24 * 29; // 29 days + const period = 3600 * 24 * 27; // 27 days const amount = parseEther('1000'); @@ -381,6 +377,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -415,6 +412,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -449,6 +447,7 @@ describe('buyCover', function () { payWitNXM: false, commissionRatio: '2501', commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { diff --git a/test/unit/Cover/createStakingPool.js b/test/unit/Cover/createStakingPool.js index 759af9675a..f1ad94f675 100644 --- a/test/unit/Cover/createStakingPool.js +++ b/test/unit/Cover/createStakingPool.js @@ -3,51 +3,53 @@ const { ethers: { utils: { parseEther }, }, + ethers, } = require('hardhat'); -const CoverMockStakingPool = artifacts.require('CoverMockStakingPool'); -const IStakingPool = artifacts.require('IStakingPool'); - describe('createStakingPool', function () { it('should create new pool', async function () { const { cover, nxm, memberRoles } = this; const { - advisoryBoardMembers: [ab1], - governanceContracts: [gv1], members: [stakingPoolCreator, stakingPoolManager], } = this.accounts; + const initialPoolFee = '5'; // 5% + const maxPoolFee = '5'; // 5% + + const depositAmount = '0'; + const trancheId = '0'; + + const productinitializationParams = [{ + productId: 0, + weight: 100, + initialPrice: '500', + targetPrice: '500' + }]; + + const firstStakingPoolAddress = await cover.stakingPool(0); + + console.log({ + firstStakingPoolAddress + }); + + await cover.connect(stakingPoolCreator).createStakingPool( + stakingPoolManager.address, + false, // isPrivatePool, + initialPoolFee, + maxPoolFee, + productinitializationParams, + depositAmount, + trancheId + ); + + const stakingPoolInstance = await ethers.getContractAt('IStakingPool', firstStakingPoolAddress); + const storedManager = await stakingPoolInstance.manager(); + assert.equal(storedManager, stakingPoolManager.address); - const productId = 0; - - const initialPrice = 260; - const targetPrice = 260; - const activeCover = parseEther('8000'); - const capacity = parseEther('10000'); - - const stakingPool = await CoverMockStakingPool.new(nxm.address, cover.address, memberRoles.address); - const capacityFactor = '1'; - - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); - await cover.connect(ab1).setInitialPrices([productId], [initialPrice]); - - await stakingPool.setStake(productId, capacity); - await stakingPool.setTargetPrice(productId, targetPrice); - await stakingPool.setUsedCapacity(productId, activeCover); - - const tx = await cover.connect(stakingPoolCreator).createStakingPool(stakingPoolManager.address); - - const receipt = await tx.wait(); - - const { stakingPoolAddress, manager, stakingPoolImplementation } = receipt.events[0].args; - - const expectedStakingPoolImplementation = await cover.stakingPoolImplementation(); + const proxyInstance = await ethers.getContractAt('MinimalBeaconProxy', firstStakingPoolAddress); - assert.equal(manager, stakingPoolManager.address); - assert.equal(stakingPoolImplementation, expectedStakingPoolImplementation); + const beacon = await proxyInstance.beacon(); - const stakingPoolInstance = await IStakingPool.at(stakingPoolAddress); - const storedManager = await stakingPoolInstance.manager(); - assert.equal(storedManager, stakingPoolManager.address); + await assert.equal(beacon, cover.address); }); }); diff --git a/test/unit/Cover/editCover.js b/test/unit/Cover/editCover.js index 332726de9e..5b9dcfb268 100644 --- a/test/unit/Cover/editCover.js +++ b/test/unit/Cover/editCover.js @@ -58,6 +58,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: increasedAmount.toString() }], { @@ -113,6 +114,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -172,6 +174,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: increasedAmount.toString() }], { @@ -231,6 +234,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: decreasedAmount.toString() }], { @@ -288,6 +292,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: increasedAmount.toString() }], { @@ -334,6 +339,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: increasedAmount.toString() }], { @@ -379,6 +385,7 @@ describe('editCover', function () { payWitNXM: false, commissionRatio: '2600', // too high commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: increasedAmount.toString() }], { diff --git a/test/unit/Cover/helpers.js b/test/unit/Cover/helpers.js index 8913f78923..4ee2881f8d 100644 --- a/test/unit/Cover/helpers.js +++ b/test/unit/Cover/helpers.js @@ -1,27 +1,55 @@ const { artifacts, ethers: { utils: { parseEther }, BigNumber } } = require('hardhat'); const { constants: { ZERO_ADDRESS } } = require('@openzeppelin/test-helpers'); const Decimal = require('decimal.js'); -const { assert, expect} = require('chai'); +const { assert, expect } = require('chai'); const { bnEqual } = require("../../../lib/helpers"); -const CoverMockStakingPool = artifacts.require('CoverMockStakingPool'); + +const DEFAULT_POOL_FEE = '5' + +const DEFAULT_PRODUCT_INITIALIZATION = [ + { + productId: 0, + weight: 100 + } +] async function createStakingPool ( cover, productId, capacity, targetPrice, activeCover, stakingPoolCreator, stakingPoolManager, currentPrice, ) { - const tx = await cover.connect(stakingPoolCreator).createStakingPool(stakingPoolManager.address); + const productinitializationParams = DEFAULT_PRODUCT_INITIALIZATION.map(p => { + p.initialPrice = currentPrice; + p.targetPrice = targetPrice; + return p; + }); + + const tx = await cover.connect(stakingPoolCreator).createStakingPool( + stakingPoolManager.address, + false, // isPrivatePool, + DEFAULT_POOL_FEE, // initialPoolFee + DEFAULT_POOL_FEE, // maxPoolFee, + productinitializationParams, + '0', // depositAmount, + '0', // trancheId + ); + + await tx.wait(); - const receipt = await tx.wait(); + const stakingPoolCount = await cover.stakingPoolCount(); - const { stakingPoolAddress } = receipt.events[0].args; + const stakingPoolIndex = stakingPoolCount.sub(1); - const stakingPool = await CoverMockStakingPool.at(stakingPoolAddress); + const stakingPoolAddress = await cover.stakingPool(stakingPoolIndex); + + const stakingPool = await ethers.getContractAt('CoverMockStakingPool', stakingPoolAddress); await stakingPool.setStake(productId, capacity); + + await stakingPool.setTargetPrice(productId, targetPrice); await stakingPool.setUsedCapacity(productId, activeCover); - await stakingPool.setPrice(productId, BigNumber.from(currentPrice).mul(1e16.toString())); // 2.6% + await stakingPool.setPrice(productId, currentPrice); // 2.6% return stakingPool; } @@ -59,13 +87,10 @@ async function buyCoverOnOnePool ( const { cover } = this; const { - governanceContracts: [gv1], members: [member1], members: [coverBuyer1, stakingPoolManager], } = this.accounts; - await cover.connect(gv1).setGlobalCapacityRatio(capacityFactor); - await createStakingPool( cover, productId, capacity, targetPriceRatio, activeCover, stakingPoolManager, stakingPoolManager, targetPriceRatio, ); @@ -84,6 +109,7 @@ async function buyCoverOnOnePool ( payWitNXM: false, commissionRatio: parseEther('0'), commissionDestination: ZERO_ADDRESS, + ipfsData: '' }, [{ poolId: '0', coverAmountInAsset: amount.toString() }], { @@ -116,8 +142,8 @@ function toDecimal (x) { } module.exports = { - createStakingPool, assertCoverFields, buyCoverOnOnePool, - MAX_COVER_PERIOD + MAX_COVER_PERIOD, + createStakingPool }; diff --git a/test/unit/Cover/index.js b/test/unit/Cover/index.js index 31830a24d3..a289a66a3d 100644 --- a/test/unit/Cover/index.js +++ b/test/unit/Cover/index.js @@ -1,7 +1,7 @@ const { takeSnapshot, revertToSnapshot } = require('../utils').evm; const setup = require('./setup'); -describe.skip('Cover unit tests', function () { +describe('Cover unit tests', function () { before(setup); beforeEach(async function () { @@ -15,7 +15,6 @@ describe.skip('Cover unit tests', function () { require('./buyCover'); require('./editCover'); require('./createStakingPool'); - require('./getGlobalActiveCoverAmountForAsset'); - // [todo] This test suite is missing - // require('./performPayoutBurn'); + require('./totalActiveCoverInAsset'); + require('./performPayoutBurn'); }); diff --git a/test/unit/Cover/performPayoutBurn.js b/test/unit/Cover/performPayoutBurn.js new file mode 100644 index 0000000000..945677c9b6 --- /dev/null +++ b/test/unit/Cover/performPayoutBurn.js @@ -0,0 +1,55 @@ +const { assert, expect } = require('chai'); +const { ethers } = require('hardhat'); +const { utils: { parseEther } } = ethers; +const { assertCoverFields, + buyCoverOnOnePool +} = require('./helpers'); +const { bnEqual } = require('../utils').helpers; + +describe.skip('performPayoutBurn', function () { + + const coverBuyFixture = { + productId: 0, + payoutAsset: 0, // ETH + period: 3600 * 24 * 30, // 30 days + + amount: parseEther('1000'), + + targetPriceRatio: '260', + priceDenominator: '10000', + activeCover: parseEther('5000'), + capacity: parseEther('10000'), + capacityFactor: '10000', + }; + + it('should perform a burn a cover with 1 segment and 1 pool allocation', async function () { + const { cover } = this; + + const { + internalContracts: [internal1] + } = this.accounts; + + const { + productId, + payoutAsset, + period, + amount, + targetPriceRatio + } = coverBuyFixture; + + const { expectedPremium, segmentId, coverId: expectedCoverId } = await buyCoverOnOnePool.call(this, coverBuyFixture); + + + const burnAmount = coverBuyFixture.amount.div(2); + + await cover.connect(internal1).performPayoutBurn( + expectedCoverId, + segmentId, + burnAmount + ); + + await assertCoverFields(cover, expectedCoverId, + { productId, payoutAsset, period: period, amount, targetPriceRatio, segmentId }, + ); + }); +}); diff --git a/test/unit/Cover/setup.js b/test/unit/Cover/setup.js index bfb1801fb5..6f7282d733 100644 --- a/test/unit/Cover/setup.js +++ b/test/unit/Cover/setup.js @@ -24,13 +24,22 @@ async function setup () { const PriceFeedOracle = await ethers.getContractFactory('PriceFeedOracle'); const ChainlinkAggregatorMock = await ethers.getContractFactory('ChainlinkAggregatorMock'); const QuotationData = await ethers.getContractFactory('CoverMockQuotationData'); - const Cover = await ethers.getContractFactory('Cover'); const MemberRolesMock = await ethers.getContractFactory('MemberRolesMock'); const CoverNFT = await ethers.getContractFactory('CoverNFT'); const TokenController = await ethers.getContractFactory('TokenControllerMock'); const NXMToken = await ethers.getContractFactory('NXMTokenMock'); const MCR = await ethers.getContractFactory('CoverMockMCR'); const StakingPool = await ethers.getContractFactory('CoverMockStakingPool'); + const CoverUtilsLib = await ethers.getContractFactory('CoverUtilsLib'); + + + const coverUtilsLib = await CoverUtilsLib.deploy(); + + const Cover = await ethers.getContractFactory('Cover', { + libraries: { + CoverUtilsLib: coverUtilsLib.address + } + }); const [owner] = await ethers.getSigners(); @@ -69,13 +78,12 @@ async function setup () { const coverAddress = getDeployAddressAfter(1); - const stakingPool = await StakingPool.deploy(nxm.address, coverAddress, memberRoles.address); + const stakingPool = await StakingPool.deploy(nxm.address, coverAddress, memberRoles.address, tokenController.address); const cover = await Cover.deploy( quotationData.address, ethers.constants.AddressZero, - stakingPool.address, futureCoverNFTAddress, - coverAddress, + stakingPool.address ); await cover.deployed(); @@ -167,6 +175,10 @@ async function setup () { }, ], ['']); + const capacityFactor = '10000'; + + await cover.connect(accounts.governanceContracts[0]).updateUintParameters([0], [capacityFactor]); + this.master = master; this.pool = pool; this.dai = dai; @@ -176,6 +188,7 @@ async function setup () { this.chainlinkDAI = chainlinkDAI; this.cover = cover; this.accounts = accounts; + this.capacityFactor = capacityFactor; } module.exports = setup; diff --git a/test/unit/Cover/getGlobalActiveCoverAmountForAsset.js b/test/unit/Cover/totalActiveCoverInAsset.js similarity index 96% rename from test/unit/Cover/getGlobalActiveCoverAmountForAsset.js rename to test/unit/Cover/totalActiveCoverInAsset.js index 149568f7b4..fb8a102428 100644 --- a/test/unit/Cover/getGlobalActiveCoverAmountForAsset.js +++ b/test/unit/Cover/totalActiveCoverInAsset.js @@ -7,7 +7,7 @@ const { const { buyCoverOnOnePool } = require('./helpers'); const { bnEqual } = require('../utils').helpers; -describe('getGlobalActiveCoverAmountForAsset', function () { +describe.skip('totalActiveCoverInAsset', function () { const ethCoverBuyFixture = { productId: 0,