diff --git a/test/unit/Cover/createStakingPool.js b/test/unit/Cover/createStakingPool.js index 7ff15b31b6..1e2899c508 100644 --- a/test/unit/Cover/createStakingPool.js +++ b/test/unit/Cover/createStakingPool.js @@ -1,46 +1,129 @@ -const { assert } = require('chai'); +const { expect } = require('chai'); const { ethers } = require('hardhat'); describe('createStakingPool', function () { - it('should create new pool', async function () { - const { cover } = this; - - const [stakingPoolCreator, stakingPoolManager] = this.accounts.members; - const initialPoolFee = '5'; // 5% - const maxPoolFee = '5'; // 5% - - const depositAmount = '0'; - const trancheId = '0'; - - const productinitializationParams = [ + const newPoolFixture = { + initialPoolFee: 5, // 5% + maxPoolFee: 5, // 5% + depositAmount: 0, + trancheId: 0, + poolId: 0, + productInitializationParams: [ { productId: 0, weight: 100, initialPrice: '500', targetPrice: '500', }, - ]; + ], + }; + + it('should create and initialize a new pool minimal beacon proxy pool', async function () { + const { cover } = this; - const firstStakingPoolAddress = await cover.stakingPool(0); + const [stakingPoolCreator, stakingPoolManager] = this.accounts.members; + + const { initialPoolFee, maxPoolFee, productInitializationParams, depositAmount, trancheId, poolId } = + newPoolFixture; + + const firstStakingPoolAddress = await cover.stakingPool(poolId); await cover.connect(stakingPoolCreator).createStakingPool( stakingPoolManager.address, false, // isPrivatePool, initialPoolFee, maxPoolFee, - productinitializationParams, + productInitializationParams, + depositAmount, + trancheId, + ); + + const stakingPoolInstance = await ethers.getContractAt('CoverMockStakingPool', firstStakingPoolAddress); + const proxyInstance = await ethers.getContractAt('MinimalBeaconProxy', firstStakingPoolAddress); + + const storedManager = await stakingPoolInstance.manager(); + expect(storedManager).to.be.equal(stakingPoolManager.address); + + const beacon = await proxyInstance.beacon(); + expect(beacon).to.be.equal(cover.address); + + // validate variable is initialized + const contractPoolId = await stakingPoolInstance.poolId(); + expect(contractPoolId).to.be.equal(poolId); + }); + + it('allows anyone to create a new pool', async function () { + const { cover } = this; + + const [stakingPoolCreator, stakingPoolManager] = this.accounts.generalPurpose; + + const { initialPoolFee, maxPoolFee, productInitializationParams, depositAmount, trancheId, poolId } = + newPoolFixture; + + const firstStakingPoolAddress = await cover.stakingPool(poolId); + + 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); + expect(storedManager).to.be.equal(stakingPoolManager.address); + }); - const proxyInstance = await ethers.getContractAt('MinimalBeaconProxy', firstStakingPoolAddress); + it('emits StakingPoolCreated event', async function () { + const { cover } = this; - const beacon = await proxyInstance.beacon(); + const [stakingPoolCreator, stakingPoolManager] = this.accounts.members; + + const { initialPoolFee, maxPoolFee, productInitializationParams, depositAmount, trancheId, poolId } = + newPoolFixture; + + const stakingPoolImplementation = await cover.stakingPoolImplementation(); + + const firstStakingPoolAddress = await cover.stakingPool(poolId); + + await expect( + cover.connect(stakingPoolCreator).createStakingPool( + stakingPoolManager.address, + false, // isPrivatePool, + initialPoolFee, + maxPoolFee, + productInitializationParams, + depositAmount, + trancheId, + ), + ) + .to.emit(cover, 'StakingPoolCreated') + .withArgs(firstStakingPoolAddress, poolId, stakingPoolManager.address, stakingPoolImplementation); + }); + + it('increments staking pool count', async function () { + const { cover } = this; + + const [stakingPoolCreator, stakingPoolManager] = this.accounts.members; + + const { initialPoolFee, maxPoolFee, productInitializationParams, depositAmount, trancheId } = newPoolFixture; + + const stakingPoolCountBefore = await cover.stakingPoolCount(); + + await cover.connect(stakingPoolCreator).createStakingPool( + stakingPoolManager.address, + false, // isPrivatePool, + initialPoolFee, + maxPoolFee, + productInitializationParams, + depositAmount, + trancheId, + ); - await assert.equal(beacon, cover.address); + const stakingPoolCountAfter = await cover.stakingPoolCount(); + expect(stakingPoolCountAfter).to.be.equal(stakingPoolCountBefore.add(1)); }); }); diff --git a/test/unit/Cover/helpers.js b/test/unit/Cover/helpers.js index 4ce3cb4d97..312c3e25e7 100644 --- a/test/unit/Cover/helpers.js +++ b/test/unit/Cover/helpers.js @@ -56,18 +56,11 @@ async function assertCoverFields( expect(segment.priceRatio).to.be.equal(targetPriceRatio); } -async function buyCoverOnOnePool({ - productId, - coverAsset, - period, - amount, - targetPriceRatio, - priceDenominator, - activeCover, - capacity, -}) { +async function buyCoverOnOnePool(params) { const { cover } = this; - const [coverBuyer, stakingPoolManager] = this.accounts.members; + const [, stakingPoolManager] = this.accounts.members; + + const { productId, capacity, activeCover, targetPriceRatio, amount } = params; await createStakingPool( cover, @@ -80,6 +73,23 @@ async function buyCoverOnOnePool({ targetPriceRatio, ); + const allocationRequest = [{ poolId: 0, coverAmountInAsset: amount }]; + + return buyCoverOnMultiplePools.call(this, { ...params, allocationRequest }); +} + +async function buyCoverOnMultiplePools({ + productId, + coverAsset, + period, + amount, + targetPriceRatio, + priceDenominator, + allocationRequest, +}) { + const { cover } = this; + const [coverBuyer] = this.accounts.members; + const expectedPremium = amount .mul(targetPriceRatio) .div(priceDenominator) @@ -100,7 +110,7 @@ async function buyCoverOnOnePool({ commissionDestination: AddressZero, ipfsData: '', }, - [{ poolId: '0', coverAmountInAsset: amount }], + allocationRequest, { value: expectedPremium }, ); @@ -129,4 +139,5 @@ module.exports = { buyCoverOnOnePool, MAX_COVER_PERIOD, createStakingPool, + buyCoverOnMultiplePools, }; diff --git a/test/unit/Cover/performStakeBurn.js b/test/unit/Cover/performStakeBurn.js index 79c4b38c5e..9b5437d5b1 100644 --- a/test/unit/Cover/performStakeBurn.js +++ b/test/unit/Cover/performStakeBurn.js @@ -1,6 +1,6 @@ const { ethers } = require('hardhat'); -const { assertCoverFields, buyCoverOnOnePool } = require('./helpers'); -const { bnEqual } = require('../utils').helpers; +const { assertCoverFields, buyCoverOnOnePool, buyCoverOnMultiplePools, createStakingPool } = require('./helpers'); +const { expect } = require('chai'); const { parseEther } = ethers.utils; const gracePeriodInDays = 120; @@ -37,7 +37,7 @@ describe('performStakeBurn', function () { const burnAmountDivisor = 2; - const burnAmount = coverBuyFixture.amount.div(burnAmountDivisor); + const burnAmount = amount.div(burnAmountDivisor); const remainingAmount = amount.sub(burnAmount); const segmentAllocation = await cover.coverSegmentAllocations(expectedCoverId, segmentId, '0'); @@ -61,11 +61,163 @@ describe('performStakeBurn', function () { const burnStakeCalledWith = await stakingPool.burnStakeCalledWith(); - bnEqual(burnStakeCalledWith.productId, productId); - bnEqual(burnStakeCalledWith.period, period); - bnEqual(burnStakeCalledWith.amount, expectedBurnAmount); + expect(burnStakeCalledWith.productId).to.be.equal(productId); + expect(burnStakeCalledWith.period).to.be.equal(period); + expect(burnStakeCalledWith.amount).to.be.equal(expectedBurnAmount); const activeCoverAmount = await cover.totalActiveCoverInAsset(coverAsset); - bnEqual(activeCoverAmount, amount.sub(burnAmount)); + expect(activeCoverAmount).to.be.equal(amount.sub(burnAmount)); + }); + + it('reverts if caller is not an internal contract', async function () { + const { cover } = this; + + const { + members: [member], + } = this.accounts; + + const { amount } = coverBuyFixture; + + const { segmentId, coverId: expectedCoverId } = await buyCoverOnOnePool.call(this, coverBuyFixture); + + const burnAmountDivisor = 2; + + const burnAmount = amount.div(burnAmountDivisor); + + await expect(cover.connect(member).performStakeBurn(expectedCoverId, segmentId, burnAmount)).to.be.revertedWith( + 'Caller is not an internal contract', + ); + }); + + it('does not update total active cover if tracking is not enabled', async function () { + const { cover } = this; + + const { + internalContracts: [internal1], + } = this.accounts; + + const { coverAsset, amount } = coverBuyFixture; + + const { segmentId, coverId: expectedCoverId } = await buyCoverOnOnePool.call(this, coverBuyFixture); + + const burnAmountDivisor = 2; + const burnAmount = amount.div(burnAmountDivisor); + + const activeCoverAmountBefore = await cover.totalActiveCoverInAsset(coverAsset); + + await cover.connect(internal1).performStakeBurn(expectedCoverId, segmentId, burnAmount); + + const activeCoverAmountAfter = await cover.totalActiveCoverInAsset(coverAsset); + expect(activeCoverAmountAfter).to.be.equal(activeCoverAmountBefore); + }); + + it('updates segment allocation cover amount in nxm', async function () { + const { cover } = this; + + const { + internalContracts: [internal1], + } = this.accounts; + + const { amount } = coverBuyFixture; + + const { segmentId, coverId: expectedCoverId } = await buyCoverOnOnePool.call(this, coverBuyFixture); + + const burnAmountDivisor = 2; + + const burnAmount = amount.div(burnAmountDivisor); + + const segmentAllocationBefore = await cover.coverSegmentAllocations(expectedCoverId, segmentId, 0); + + const expectedBurnAmount = segmentAllocationBefore.coverAmountInNXM.div(burnAmountDivisor); + + await cover.connect(internal1).performStakeBurn(expectedCoverId, segmentId, burnAmount); + + const segmentAllocationAfter = await cover.coverSegmentAllocations(expectedCoverId, segmentId, 0); + expect(segmentAllocationAfter.coverAmountInNXM).to.be.equal( + segmentAllocationBefore.coverAmountInNXM.sub(expectedBurnAmount), + ); + }); + + it('should perform a burn on a cover with 1 segment and 2 pool allocations', async function () { + const { cover } = this; + + const { + internalContracts: [internal1], + members: [, stakingPoolManager], + emergencyAdmin, + } = this.accounts; + + const { productId, coverAsset, period, amount, targetPriceRatio, capacity, activeCover } = coverBuyFixture; + + await cover.connect(emergencyAdmin).enableActiveCoverAmountTracking([], []); + await cover.connect(emergencyAdmin).commitActiveCoverAmounts(); + + const amountOfPools = 4; + + const amountPerPool = amount.div(amountOfPools); + const allocationRequest = []; + + for (let i = 0; i < amountOfPools; i++) { + await createStakingPool( + cover, + productId, + capacity, + targetPriceRatio, + activeCover, + stakingPoolManager, + stakingPoolManager, + targetPriceRatio, + ); + allocationRequest.push({ poolId: i, coverAmountInAsset: amountPerPool }); + } + + const { segmentId, coverId: expectedCoverId } = await buyCoverOnMultiplePools.call(this, { + ...coverBuyFixture, + allocationRequest, + }); + + const burnAmountDivisor = 2; + + const burnAmount = amount.div(burnAmountDivisor); + const remainingAmount = amount.sub(burnAmount); + + const segmentAllocationsBefore = []; + for (let i = 0; i < amountOfPools; i++) { + const segmentAllocationBefore = await cover.coverSegmentAllocations(expectedCoverId, segmentId, i); + segmentAllocationsBefore.push(segmentAllocationBefore); + } + + const expectedBurnAmountPerPool = segmentAllocationsBefore[0].coverAmountInNXM.div(burnAmountDivisor); + + await cover.connect(internal1).performStakeBurn(expectedCoverId, segmentId, burnAmount); + + await assertCoverFields(cover, expectedCoverId, { + productId, + coverAsset, + period, + amount: remainingAmount, + targetPriceRatio, + gracePeriodInDays, + segmentId, + amountPaidOut: burnAmount, + }); + + const activeCoverAmount = await cover.totalActiveCoverInAsset(coverAsset); + expect(activeCoverAmount).to.be.equal(amount.sub(burnAmount)); + + for (let i = 0; i < amountOfPools; i++) { + const stakingPool = await ethers.getContractAt('CoverMockStakingPool', await cover.stakingPool(i)); + + const burnStakeCalledWith = await stakingPool.burnStakeCalledWith(); + + expect(burnStakeCalledWith.productId).to.be.equal(productId); + expect(burnStakeCalledWith.period).to.be.equal(period); + expect(burnStakeCalledWith.amount).to.be.equal(expectedBurnAmountPerPool); + + const segmentAllocationAfter = await cover.coverSegmentAllocations(expectedCoverId, segmentId, i); + expect(segmentAllocationAfter.coverAmountInNXM).to.be.equal( + segmentAllocationsBefore[i].coverAmountInNXM.sub(expectedBurnAmountPerPool), + ); + } }); });