From 80e9aace34116f100c464e56f1310cb5407da9b7 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:26:09 -0300 Subject: [PATCH 01/19] withdraw unit tests --- contracts/mocks/TokenControllerMock.sol | 3 +- contracts/modules/staking/StakingPool.sol | 9 +- test/unit/StakingPool/index.js | 1 + test/unit/StakingPool/withdraw.js | 489 ++++++++++++++++++++++ 4 files changed, 499 insertions(+), 3 deletions(-) create mode 100644 test/unit/StakingPool/withdraw.js diff --git a/contracts/mocks/TokenControllerMock.sol b/contracts/mocks/TokenControllerMock.sol index 09f2cb3e2b..2b6cc8327e 100644 --- a/contracts/mocks/TokenControllerMock.sol +++ b/contracts/mocks/TokenControllerMock.sol @@ -77,9 +77,10 @@ contract TokenControllerMock is MasterAwareV2 { token().operatorTransfer(from, amount); } - function withdrawNXMStakeAndRewards(address /*to*/, uint stakeToWithdraw, uint rewardsToWithdraw, uint poolId) external { + function withdrawNXMStakeAndRewards(address to, uint stakeToWithdraw, uint rewardsToWithdraw, uint poolId) external { stakingPoolNXMBalances[poolId].deposits -= uint128(stakeToWithdraw); stakingPoolNXMBalances[poolId].rewards -= uint128(rewardsToWithdraw); + token().transfer(to, stakeToWithdraw + rewardsToWithdraw); } function burnStakedNXM(uint amount, uint poolId) external { diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index 77ec3fbc2f..9d6a8dcaba 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -143,6 +143,8 @@ contract StakingPool is IStakingPool, ERC721 { // smallest unit we can allocate is 1e18 / 100 = 1e16 = 0.01 NXM uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM; + event Withdraw(address indexed src, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn); + modifier onlyCoverContract { require(msg.sender == coverContract, "StakingPool: Only Cover contract can call this function"); _; @@ -582,8 +584,11 @@ contract StakingPool is IStakingPool, ERC721 { deposit.rewardsShares = 0; } - deposits[tokenId][trancheId] = deposit; - } + deposits[tokenId][trancheId] = deposit; + + emit Withdraw(msg.sender, tokenId, trancheId, withdrawnStake, withdrawnRewards); + } + tokenController.withdrawNXMStakeAndRewards( ownerOf(tokenId), diff --git a/test/unit/StakingPool/index.js b/test/unit/StakingPool/index.js index 220da502d4..3eeef9c4a5 100644 --- a/test/unit/StakingPool/index.js +++ b/test/unit/StakingPool/index.js @@ -23,4 +23,5 @@ describe('StakingPool unit tests', function () { require('./setPoolFee'); require('./setPoolPrivacy'); require('./setProducts'); + require('./withdraw'); }); diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js new file mode 100644 index 0000000000..9e1b7046ce --- /dev/null +++ b/test/unit/StakingPool/withdraw.js @@ -0,0 +1,489 @@ +const { parseEther } = require('ethers/lib/utils'); +const { ethers, expect } = require('hardhat'); +const { daysToSeconds } = require('../../../lib/helpers'); +const { setEtherBalance, increaseTime, mineNextBlock } = require('../../utils/evm'); +const { getTranches, TRANCHE_DURATION, getNewRewardShares } = require('./helpers'); + +describe('withdraw', function () { + const product0 = { + productId: 0, + weight: 100, + initialPrice: '500', + targetPrice: '500', + }; + + const initializeParams = { + poolId: 0, + isPrivatePool: false, + initialPoolFee: 5, // 5% + maxPoolFee: 5, // 5% + productInitializationParams: [product0], + }; + + const depositToFixture = { + poolId: 0, + initialPoolFee: 5, // 5% + maxPoolFee: 5, // 5% + productInitializationParams: [ + { + productId: 0, + weight: 100, + initialPrice: 500, + targetPrice: 500, + }, + ], + amount: parseEther('100'), + trancheId: 0, + tokenId: 0, + destination: ethers.constants.AddressZero, + depositNftId: 1, + }; + + // Rewards allocation + const allocationRequest = { + productId: 0, + coverId: 0, + amount: depositToFixture.amount, + period: daysToSeconds(10), + }; + + const allocationConfig = { + gracePeriod: daysToSeconds(10), + globalCapacityRatio: 20000, + capacityReductionRatio: 0, + rewardRatio: 5000, + globalMinPrice: 10000, + }; + + beforeEach(async function () { + const { + stakingPool, + cover, + accounts: { defaultSender: manager }, + } = this; + + const { poolId, initialPoolFee, maxPoolFee, productInitializationParams, isPrivatePool } = initializeParams; + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + await setEtherBalance(coverSigner.address, ethers.utils.parseEther('1')); + + await stakingPool + .connect(coverSigner) + .initialize(manager.address, isPrivatePool, initialPoolFee, maxPoolFee, productInitializationParams, poolId); + }); + + it('reverts if trying to withdraw stake locked in governance', async function () { + const { + nxm, + stakingPool, + accounts: { + defaultSender: manager, + members: [user], + }, + } = this; + + const { amount, tokenId, destination } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + await increaseTime(TRANCHE_DURATION); + + // Simulate manager lock in governance + await nxm.setLock(manager.address, 1e6); + + const withdrawStake = true; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + await expect( + stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]), + ).to.be.revertedWith( + 'StakingPool: While the pool manager is locked for governance voting only rewards can be withdrawn', + ); + }); + + it('allows to withdraw only stake', async function () { + const { + nxm, + cover, + stakingPool, + tokenController, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + + await increaseTime(TRANCHE_DURATION); + + const withdrawStake = true; + const withdrawRewards = false; + const trancheIds = [firstActiveTrancheId]; + + const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceBefore = await nxm.balanceOf(user.address); + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceAfter = await nxm.balanceOf(user.address); + const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + + expect(depositBefore.stakeShares).to.be.eq(Math.sqrt(amount)); + expect(depositAfter.stakeShares).to.be.eq(0); + + expect(TCbalanceBefore).to.be.eq(amount); + expect(TCbalanceAfter).to.be.eq(0); + + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); + }); + + it('allows to withdraw only rewards', async function () { + const { + nxm, + cover, + stakingPool, + tokenController, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + + const withdrawStake = false; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceBefore = await nxm.balanceOf(user.address); + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + + const rewardShares = await getNewRewardShares({ + stakingPool, + initialStakeShares: 0, + stakeSharesIncrease: depositBefore.stakeShares, + initialTrancheId: firstActiveTrancheId, + newTrancheId: firstActiveTrancheId, + }); + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + + await increaseTime(TRANCHE_DURATION); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceAfter = await nxm.balanceOf(user.address); + const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + + expect(depositBefore.rewardsShares).to.be.eq(rewardShares); + expect(depositAfter.rewardsShares).to.be.eq(0); + + expect(TCbalanceBefore).to.be.eq(amount); + const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); + const rewardsWithdrawn = depositBefore.rewardsShares + .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) + .add(depositBefore.pendingRewards); + expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn)); + + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn)); + }); + + it('allows to withdraw stake only if tranche is expired', async function () { + const { + tokenController, + nxm, + stakingPool, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const withdrawStake = true; + const withdrawRewards = false; + const trancheIds = [firstActiveTrancheId]; + + const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceBefore = await nxm.balanceOf(user.address); + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const userBalanceAfter = await nxm.balanceOf(user.address); + const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + + expect(depositBefore.stakeShares).to.be.eq(depositAfter.stakeShares); + expect(userBalanceBefore).to.be.eq(userBalanceAfter); + expect(TCbalanceBefore).to.be.eq(TCbalanceAfter); + }); + + it('allows to withdraw stake and rewards from multiple tranches', async function () { + const { + nxm, + cover, + stakingPool, + tokenController, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + const TRANCHES_NUMBER = 5; + + const withdrawStake = false; + const withdrawRewards = true; + const trancheIds = []; + + const userBalanceBefore = await nxm.balanceOf(user.address); + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + + for (let i = 0; i < TRANCHES_NUMBER; i++) { + const { firstActiveTrancheId: currentTranche } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: currentTranche, + tokenId, + destination, + }, + ]); + + trancheIds.push(currentTranche); + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + } + + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + + const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; + const depositBefore = await stakingPool.deposits(depositNftId, trancheIds[0]); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const depositAfter = await stakingPool.deposits(depositNftId, lastTranche); + const userBalanceAfter = await nxm.balanceOf(user.address); + + expect(depositAfter.rewardsShares).to.be.eq(0); + + expect(TCbalanceBefore).to.be.eq(0); + const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(lastTranche); + // const rewardsWithdrawn = depositBefore.rewardsShares + // .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) + // .add(depositBefore.pendingRewards); + + // expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn)); + }); + + it('update tranches', async function () { + const { + cover, + stakingPool, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + + const withdrawStake = false; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + const activeStakeBefore = await stakingPool.activeStake(); + const accNxmPerRewardsShareBefore = await stakingPool.accNxmPerRewardsShare(); + const lastAccNxmUpdateBefore = await stakingPool.lastAccNxmUpdate(); + const stakeSharesSupplyBefore = await stakingPool.stakeSharesSupply(); + const rewardsSharesSupplyBefore = await stakingPool.rewardsSharesSupply(); + + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const activeStakeAfter = await stakingPool.activeStake(); + const accNxmPerRewardsShareAfter = await stakingPool.accNxmPerRewardsShare(); + const lastAccNxmUpdateAfter = await stakingPool.lastAccNxmUpdate(); + const stakeSharesSupplyAfter = await stakingPool.stakeSharesSupply(); + const rewardsSharesSupplyAfter = await stakingPool.rewardsSharesSupply(); + + expect(activeStakeAfter).to.not.eq(activeStakeBefore); + expect(accNxmPerRewardsShareAfter).to.not.eq(accNxmPerRewardsShareBefore); + expect(lastAccNxmUpdateAfter).to.not.eq(lastAccNxmUpdateBefore); + expect(stakeSharesSupplyAfter).to.not.eq(stakeSharesSupplyBefore); + expect(rewardsSharesSupplyAfter).to.not.eq(rewardsSharesSupplyBefore); + }); + + it('anyone can call to withdraw stake and rewards for a token id', async function () { + const { + cover, + stakingPool, + accounts: { + members: [user], + nonMembers: [randomUser], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + + const withdrawStake = true; + const withdrawRewards = false; + const trancheIds = [firstActiveTrancheId]; + + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + + await increaseTime(TRANCHE_DURATION); + + expect( + await stakingPool + .connect(randomUser) + .withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), + ).to.not.be.reverted; + }); + + it('should emit some event', async function () { + const { + nxm, + cover, + stakingPool, + accounts: { + members: [user], + }, + } = this; + + const { amount, tokenId, destination, depositNftId } = depositToFixture; + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + const TRANCHES_NUMBER = 3; + + const withdrawStake = true; + const withdrawRewards = true; + const trancheIds = []; + + for (let i = 0; i < TRANCHES_NUMBER; i++) { + const { firstActiveTrancheId: currentTranche } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: currentTranche, + tokenId, + destination, + }, + ]); + + trancheIds.push(currentTranche); + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + } + + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + + const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; + const depositBefore = await stakingPool.deposits(depositNftId, trancheIds[0]); + + const rewardShares = await getNewRewardShares({ + stakingPool, + initialStakeShares: 0, + stakeSharesIncrease: depositBefore.stakeShares, + initialTrancheId: trancheIds[0], + newTrancheId: lastTranche, + }); + + await expect( + stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), + ) + .to.emit(stakingPool, 'Withdraw') + //.withArgs(user.address, depositNftId, trancheIds[0], 0, 0) + .emit(stakingPool, 'Withdraw') + //.withArgs(user.address, depositNftId, trancheIds[1], 0, 0) + .emit(stakingPool, 'Withdraw'); + //.withArgs(user.address, depositNftId, trancheIds[2], 0, 0); + }); +}); From 0d887392a59f67c5716f66eabb5ae986d3b8368e Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Fri, 2 Dec 2022 10:21:42 -0300 Subject: [PATCH 02/19] improve tests --- test/unit/StakingPool/helpers.js | 13 ++ test/unit/StakingPool/setup.js | 2 + test/unit/StakingPool/withdraw.js | 212 +++++++++++++++++++++--------- 3 files changed, 163 insertions(+), 64 deletions(-) diff --git a/test/unit/StakingPool/helpers.js b/test/unit/StakingPool/helpers.js index 8b0bdcfade..6be0b4b72e 100644 --- a/test/unit/StakingPool/helpers.js +++ b/test/unit/StakingPool/helpers.js @@ -190,6 +190,18 @@ async function generateRewards(stakingPool, signer, period = daysToSeconds(10), await stakingPool.connect(signer).requestAllocation(amount, previousPremium, allocationRequest); } +async function calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, trancheId) { + const { accNxmPerRewardShareAtExpiry, stakeAmountAtExpiry, stakeShareSupplyAtExpiry } = + await stakingPool.expiredTranches(trancheId); + + return { + rewards: deposit.rewardsShares + .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) + .add(deposit.pendingRewards), + stake: stakeAmountAtExpiry.mul(deposit.stakeShares).div(stakeShareSupplyAtExpiry), + }; +} + module.exports = { setTime, calculateBasePrice, @@ -206,6 +218,7 @@ module.exports = { getNewRewardShares, estimateStakeShares, generateRewards, + calculateStakeAndRewardsWithdrawAmounts, TRANCHE_DURATION, BUCKET_DURATION, MAX_ACTIVE_TRANCHES, diff --git a/test/unit/StakingPool/setup.js b/test/unit/StakingPool/setup.js index 98193353d8..c264a22bf4 100644 --- a/test/unit/StakingPool/setup.js +++ b/test/unit/StakingPool/setup.js @@ -78,6 +78,8 @@ async function setup() { await master.enrollGovernance(governanceContract.address); } + await tokenController.changeMasterAddress(master.address); + const config = { REWARD_BONUS_PER_TRANCHE_RATIO: await stakingPool.REWARD_BONUS_PER_TRANCHE_RATIO(), REWARD_BONUS_PER_TRANCHE_DENOMINATOR: await stakingPool.REWARD_BONUS_PER_TRANCHE_DENOMINATOR(), diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 9e1b7046ce..b2de9f3aab 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -1,8 +1,14 @@ +const { BigNumber } = require('ethers'); const { parseEther } = require('ethers/lib/utils'); const { ethers, expect } = require('hardhat'); const { daysToSeconds } = require('../../../lib/helpers'); const { setEtherBalance, increaseTime, mineNextBlock } = require('../../utils/evm'); -const { getTranches, TRANCHE_DURATION, getNewRewardShares } = require('./helpers'); +const { + getTranches, + TRANCHE_DURATION, + getNewRewardShares, + calculateStakeAndRewardsWithdrawAmounts, +} = require('./helpers'); describe('withdraw', function () { const product0 = { @@ -18,20 +24,13 @@ describe('withdraw', function () { initialPoolFee: 5, // 5% maxPoolFee: 5, // 5% productInitializationParams: [product0], + ipfsDescriptionHash: 'Description Hash', }; + const tempRewardsAmount = parseEther('10'); + const depositToFixture = { - poolId: 0, - initialPoolFee: 5, // 5% - maxPoolFee: 5, // 5% - productInitializationParams: [ - { - productId: 0, - weight: 100, - initialPrice: 500, - targetPrice: 500, - }, - ], + ...initializeParams, amount: parseEther('100'), trancheId: 0, tokenId: 0, @@ -59,17 +58,34 @@ describe('withdraw', function () { const { stakingPool, cover, - accounts: { defaultSender: manager }, + tokenController, + accounts: { + defaultSender: manager, + internalContracts: [internal], + }, } = this; - const { poolId, initialPoolFee, maxPoolFee, productInitializationParams, isPrivatePool } = initializeParams; + const { poolId, initialPoolFee, maxPoolFee, productInitializationParams, isPrivatePool, ipfsDescriptionHash } = + initializeParams; const coverSigner = await ethers.getImpersonatedSigner(cover.address); await setEtherBalance(coverSigner.address, ethers.utils.parseEther('1')); await stakingPool .connect(coverSigner) - .initialize(manager.address, isPrivatePool, initialPoolFee, maxPoolFee, productInitializationParams, poolId); + .initialize( + manager.address, + isPrivatePool, + initialPoolFee, + maxPoolFee, + productInitializationParams, + poolId, + ipfsDescriptionHash, + ); + + const internalSigner = await ethers.getImpersonatedSigner(internal.address); + + await tokenController.connect(internalSigner).mintStakingPoolNXMRewards(tempRewardsAmount, initializeParams.poolId); }); it('reverts if trying to withdraw stake locked in governance', async function () { @@ -116,7 +132,6 @@ describe('withdraw', function () { nxm, cover, stakingPool, - tokenController, accounts: { members: [user], }, @@ -136,7 +151,6 @@ describe('withdraw', function () { ]); const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); await increaseTime(TRANCHE_DURATION); @@ -147,21 +161,65 @@ describe('withdraw', function () { const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); - const TCbalanceAfter = await nxm.balanceOf(tokenController.address); expect(depositBefore.stakeShares).to.be.eq(Math.sqrt(amount)); expect(depositAfter.stakeShares).to.be.eq(0); + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); + }); + + it('transfers nxm stake and rewards from token controller to nft owner', async function () { + const { + nxm, + cover, + stakingPool, + tokenController, + accounts: { + members: [user], + }, + } = this; - expect(TCbalanceBefore).to.be.eq(amount); - expect(TCbalanceAfter).to.be.eq(0); + const { amount, tokenId, destination, depositNftId } = depositToFixture; - expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); + const { firstActiveTrancheId } = await getTranches(); + + await stakingPool.connect(user).depositTo([ + { + amount, + trancheId: firstActiveTrancheId, + tokenId, + destination, + }, + ]); + + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + + await increaseTime(TRANCHE_DURATION); + + const withdrawStake = true; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + + const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + + expect(TCbalanceBefore).to.be.eq(amount.add(tempRewardsAmount)); + + const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); + const rewardsWithdrawn = deposit.rewardsShares + .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) + .add(deposit.pendingRewards); + + expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn).sub(amount)); }); it('allows to withdraw only rewards', async function () { @@ -169,7 +227,6 @@ describe('withdraw', function () { nxm, cover, stakingPool, - tokenController, accounts: { members: [user], }, @@ -196,7 +253,6 @@ describe('withdraw', function () { const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); const rewardShares = await getNewRewardShares({ stakingPool, @@ -213,17 +269,14 @@ describe('withdraw', function () { const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); - const TCbalanceAfter = await nxm.balanceOf(tokenController.address); expect(depositBefore.rewardsShares).to.be.eq(rewardShares); expect(depositAfter.rewardsShares).to.be.eq(0); - expect(TCbalanceBefore).to.be.eq(amount); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = depositBefore.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) .add(depositBefore.pendingRewards); - expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn)); expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn)); }); @@ -252,19 +305,22 @@ describe('withdraw', function () { ]); const withdrawStake = true; - const withdrawRewards = false; + const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); const TCbalanceBefore = await nxm.balanceOf(tokenController.address); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await expect( + stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), + ).to.not.be.reverted; const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + // Nothing changes expect(depositBefore.stakeShares).to.be.eq(depositAfter.stakeShares); expect(userBalanceBefore).to.be.eq(userBalanceAfter); expect(TCbalanceBefore).to.be.eq(TCbalanceAfter); @@ -280,18 +336,15 @@ describe('withdraw', function () { members: [user], }, } = this; - const { amount, tokenId, destination, depositNftId } = depositToFixture; - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - const TRANCHES_NUMBER = 5; - const withdrawStake = false; - const withdrawRewards = true; + const TRANCHES_NUMBER = 5; const trancheIds = []; - const userBalanceBefore = await nxm.balanceOf(user.address); - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + const withdrawStake = true; + const withdrawRewards = true; + const coverSigner = await ethers.getImpersonatedSigner(cover.address); for (let i = 0; i < TRANCHES_NUMBER; i++) { const { firstActiveTrancheId: currentTranche } = await getTranches(); @@ -310,26 +363,38 @@ describe('withdraw', function () { await mineNextBlock(); } - await increaseTime(TRANCHE_DURATION); - await mineNextBlock(); + const depositsBeforeWithdraw = {}; + for (const tranche of trancheIds) { + depositsBeforeWithdraw[tranche] = await stakingPool.deposits(depositNftId, tranche); + } - const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; - const depositBefore = await stakingPool.deposits(depositNftId, trancheIds[0]); + const userBalanceBefore = await nxm.balanceOf(user.address); + const TCbalanceBefore = await nxm.balanceOf(tokenController.address); await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; const depositAfter = await stakingPool.deposits(depositNftId, lastTranche); const userBalanceAfter = await nxm.balanceOf(user.address); + const TCbalanceAfter = await nxm.balanceOf(tokenController.address); expect(depositAfter.rewardsShares).to.be.eq(0); - expect(TCbalanceBefore).to.be.eq(0); - const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(lastTranche); - // const rewardsWithdrawn = depositBefore.rewardsShares - // .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) - // .add(depositBefore.pendingRewards); + let rewardsWithdrawn = BigNumber.from(0); + let stakeWithdrawn = BigNumber.from(0); + for (const tranche of trancheIds) { + const { rewards, stake } = await calculateStakeAndRewardsWithdrawAmounts( + stakingPool, + depositsBeforeWithdraw[tranche], + tranche, + ); + + rewardsWithdrawn = rewardsWithdrawn.add(rewards); + stakeWithdrawn = stakeWithdrawn.add(stake); + } - // expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn).add(stakeWithdrawn)); + expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); }); it('update tranches', async function () { @@ -390,6 +455,7 @@ describe('withdraw', function () { const { cover, stakingPool, + nxm, accounts: { members: [user], nonMembers: [randomUser], @@ -417,18 +483,29 @@ describe('withdraw', function () { await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); await increaseTime(TRANCHE_DURATION); + const userBalanceBefore = await nxm.balanceOf(user.address); + const randomUserBalanceBefore = await nxm.balanceOf(randomUser.address); + expect( await stakingPool .connect(randomUser) .withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), ).to.not.be.reverted; + + const { stake } = await calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, firstActiveTrancheId); + + const userBalanceAfter = await nxm.balanceOf(user.address); + const randomUserBalanceAfter = await nxm.balanceOf(randomUser.address); + + expect(randomUserBalanceAfter).to.eq(randomUserBalanceBefore); + expect(userBalanceAfter).to.eq(userBalanceBefore.add(stake)); }); it('should emit some event', async function () { const { - nxm, cover, stakingPool, accounts: { @@ -438,11 +515,12 @@ describe('withdraw', function () { const { amount, tokenId, destination, depositNftId } = depositToFixture; const coverSigner = await ethers.getImpersonatedSigner(cover.address); + const TRANCHES_NUMBER = 3; + const trancheIds = []; const withdrawStake = true; const withdrawRewards = true; - const trancheIds = []; for (let i = 0; i < TRANCHES_NUMBER; i++) { const { firstActiveTrancheId: currentTranche } = await getTranches(); @@ -462,28 +540,34 @@ describe('withdraw', function () { await mineNextBlock(); } - await increaseTime(TRANCHE_DURATION); - await mineNextBlock(); + const depositsBeforeWithdraw = {}; + for (const tranche of trancheIds) { + depositsBeforeWithdraw[tranche] = await stakingPool.deposits(depositNftId, tranche); + } - const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; - const depositBefore = await stakingPool.deposits(depositNftId, trancheIds[0]); + await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); - const rewardShares = await getNewRewardShares({ - stakingPool, - initialStakeShares: 0, - stakeSharesIncrease: depositBefore.stakeShares, - initialTrancheId: trancheIds[0], - newTrancheId: lastTranche, - }); + const stakes = []; + const rewards = []; + for (const tranche of trancheIds) { + const { rewards: currentReward, stake } = await calculateStakeAndRewardsWithdrawAmounts( + stakingPool, + depositsBeforeWithdraw[tranche], + tranche, + ); + + stakes.push(stake); + rewards.push(currentReward); + } await expect( stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), ) .to.emit(stakingPool, 'Withdraw') - //.withArgs(user.address, depositNftId, trancheIds[0], 0, 0) - .emit(stakingPool, 'Withdraw') - //.withArgs(user.address, depositNftId, trancheIds[1], 0, 0) - .emit(stakingPool, 'Withdraw'); - //.withArgs(user.address, depositNftId, trancheIds[2], 0, 0); + .withArgs(user.address, depositNftId, trancheIds[1], stakes[1], rewards[1]) + .to.emit(stakingPool, 'Withdraw') + .withArgs(user.address, depositNftId, trancheIds[2], stakes[2], rewards[2]) + .to.emit(stakingPool, 'Withdraw') + .withArgs(user.address, depositNftId, trancheIds[2], stakes[2], rewards[2]); }); }); From f380ebee11000077eac9fc13b59af3c0239733b2 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Fri, 2 Dec 2022 10:34:29 -0300 Subject: [PATCH 03/19] move event --- contracts/interfaces/IStakingPool.sol | 2 ++ contracts/modules/staking/StakingPool.sol | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IStakingPool.sol b/contracts/interfaces/IStakingPool.sol index 1bb1871afc..c8a8013eea 100644 --- a/contracts/interfaces/IStakingPool.sol +++ b/contracts/interfaces/IStakingPool.sol @@ -140,4 +140,6 @@ interface IStakingPool { event PoolFeeChanged(address indexed manager, uint newFee); event PoolDescriptionSet(uint poolId, string ipfsDescriptionHash); + + event Withdraw(address indexed src, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn); } diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index 9d6a8dcaba..1fc9d69b80 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -143,8 +143,6 @@ contract StakingPool is IStakingPool, ERC721 { // smallest unit we can allocate is 1e18 / 100 = 1e16 = 0.01 NXM uint public constant NXM_PER_ALLOCATION_UNIT = ONE_NXM / ALLOCATION_UNITS_PER_NXM; - event Withdraw(address indexed src, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn); - modifier onlyCoverContract { require(msg.sender == coverContract, "StakingPool: Only Cover contract can call this function"); _; From b5e2abc28ef0326ef2409ce1c47a9d03f49c6751 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:20:23 -0300 Subject: [PATCH 04/19] fix emit args by not using acc variable and doing calculation after expiration --- contracts/modules/staking/StakingPool.sol | 73 +++++++++++++---------- test/unit/StakingPool/withdraw.js | 13 ++-- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index 1fc9d69b80..e029b5a49c 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -546,46 +546,53 @@ contract StakingPool is IStakingPool, ERC721 { Deposit memory deposit = deposits[tokenId][trancheId]; - // can withdraw stake only if the tranche is expired - if (withdrawStake && trancheId < _firstActiveTrancheId) { - - // Deposit withdrawals are not permitted while the manager is locked in governance to - // prevent double voting. - require( - managerLockedInGovernanceUntil < block.timestamp, - "StakingPool: While the pool manager is locked for governance voting only rewards can be withdrawn" - ); - - // calculate the amount of nxm for this deposit - uint stake = expiredTranches[trancheId].stakeAmountAtExpiry; - uint stakeShareSupply = expiredTranches[trancheId].stakeShareSupplyAtExpiry; - withdrawnStake += stake * deposit.stakeShares / stakeShareSupply; + { + uint trancheRewardsToWithdraw; + uint trancheStakeToWithdraw; + + // can withdraw stake only if the tranche is expired + if (withdrawStake && trancheId < _firstActiveTrancheId) { + + // Deposit withdrawals are not permitted while the manager is locked in governance to + // prevent double voting. + require( + managerLockedInGovernanceUntil < block.timestamp, + "StakingPool: While the pool manager is locked for governance voting only rewards can be withdrawn" + ); + + // calculate the amount of nxm for this deposit + uint stake = expiredTranches[trancheId].stakeAmountAtExpiry; + uint stakeShareSupply = expiredTranches[trancheId].stakeShareSupplyAtExpiry; + trancheStakeToWithdraw = stake * deposit.stakeShares / stakeShareSupply; + withdrawnStake += trancheStakeToWithdraw; + + // mark as withdrawn + deposit.stakeShares = 0; + } - // mark as withdrawn - deposit.stakeShares = 0; - } + if (withdrawRewards) { - if (withdrawRewards) { + // if the tranche is expired, use the accumulator value saved at expiration time + uint accNxmPerRewardShareToUse = trancheId < _firstActiveTrancheId + ? expiredTranches[trancheId].accNxmPerRewardShareAtExpiry + : _accNxmPerRewardsShare; - // if the tranche is expired, use the accumulator value saved at expiration time - uint accNxmPerRewardShareToUse = trancheId < _firstActiveTrancheId - ? expiredTranches[trancheId].accNxmPerRewardShareAtExpiry - : _accNxmPerRewardsShare; + // calculate reward since checkpoint + uint newRewardPerShare = accNxmPerRewardShareToUse.uncheckedSub(deposit.lastAccNxmPerRewardShare); + trancheRewardsToWithdraw = newRewardPerShare * deposit.rewardsShares + deposit.pendingRewards; + withdrawnRewards += trancheRewardsToWithdraw; - // calculate reward since checkpoint - uint newRewardPerShare = accNxmPerRewardShareToUse.uncheckedSub(deposit.lastAccNxmPerRewardShare); - withdrawnRewards += newRewardPerShare * deposit.rewardsShares + deposit.pendingRewards; + // save checkpoint + deposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare; + deposit.pendingRewards = 0; + deposit.rewardsShares = 0; + } - // save checkpoint - deposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare; - deposit.pendingRewards = 0; - deposit.rewardsShares = 0; + emit Withdraw(msg.sender, tokenId, trancheId, trancheStakeToWithdraw, trancheRewardsToWithdraw); } - deposits[tokenId][trancheId] = deposit; - - emit Withdraw(msg.sender, tokenId, trancheId, withdrawnStake, withdrawnRewards); - } + deposits[tokenId][trancheId] = deposit; + } tokenController.withdrawNXMStakeAndRewards( diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index b2de9f3aab..bc9b5caaf7 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -352,7 +352,7 @@ describe('withdraw', function () { { amount, trancheId: currentTranche, - tokenId, + tokenId: i == 0 ? tokenId : depositNftId, destination, }, ]); @@ -527,9 +527,9 @@ describe('withdraw', function () { await stakingPool.connect(user).depositTo([ { - amount, + amount: amount.mul(i * 5 + 1), trancheId: currentTranche, - tokenId, + tokenId: i == 0 ? tokenId : depositNftId, destination, }, ]); @@ -545,7 +545,8 @@ describe('withdraw', function () { depositsBeforeWithdraw[tranche] = await stakingPool.deposits(depositNftId, tranche); } - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + // Update expiredTranches + await stakingPool.updateTranches(true); const stakes = []; const rewards = []; @@ -564,9 +565,9 @@ describe('withdraw', function () { stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), ) .to.emit(stakingPool, 'Withdraw') - .withArgs(user.address, depositNftId, trancheIds[1], stakes[1], rewards[1]) + .withArgs(user.address, depositNftId, trancheIds[0], stakes[0], rewards[0]) .to.emit(stakingPool, 'Withdraw') - .withArgs(user.address, depositNftId, trancheIds[2], stakes[2], rewards[2]) + .withArgs(user.address, depositNftId, trancheIds[1], stakes[1], rewards[1]) .to.emit(stakingPool, 'Withdraw') .withArgs(user.address, depositNftId, trancheIds[2], stakes[2], rewards[2]); }); From 7405df617e1533dd5152e9b4bc090ecacf4cbaf8 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:24:35 -0300 Subject: [PATCH 05/19] lint --- test/unit/StakingPool/withdraw.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index bc9b5caaf7..bf21f6db20 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -352,7 +352,7 @@ describe('withdraw', function () { { amount, trancheId: currentTranche, - tokenId: i == 0 ? tokenId : depositNftId, + tokenId: i === 0 ? tokenId : depositNftId, destination, }, ]); @@ -489,10 +489,8 @@ describe('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const randomUserBalanceBefore = await nxm.balanceOf(randomUser.address); - expect( - await stakingPool - .connect(randomUser) - .withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), + await expect( + stakingPool.connect(randomUser).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), ).to.not.be.reverted; const { stake } = await calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, firstActiveTrancheId); @@ -529,7 +527,7 @@ describe('withdraw', function () { { amount: amount.mul(i * 5 + 1), trancheId: currentTranche, - tokenId: i == 0 ? tokenId : depositNftId, + tokenId: i === 0 ? tokenId : depositNftId, destination, }, ]); From a5be4bf237029c5a97ba3af3f7863d65b9a6ebba Mon Sep 17 00:00:00 2001 From: shark0der Date: Mon, 5 Dec 2022 16:31:15 +0200 Subject: [PATCH 06/19] Add explicit return statement to StakingPool.withdraw --- contracts/modules/staking/StakingPool.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index e029b5a49c..874cbe2668 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -594,13 +594,14 @@ contract StakingPool is IStakingPool, ERC721 { deposits[tokenId][trancheId] = deposit; } - tokenController.withdrawNXMStakeAndRewards( ownerOf(tokenId), withdrawnStake, withdrawnRewards, poolId ); + + return (withdrawnStake, withdrawnRewards); } function requestAllocation( From af6690ac33390fef8cfe0a2e633bbf0e8424328e Mon Sep 17 00:00:00 2001 From: shark0der Date: Mon, 5 Dec 2022 18:14:50 +0200 Subject: [PATCH 07/19] Change allocateCapacity to requestAllocation and apply some reformatting --- test/unit/StakingPool/withdraw.js | 137 +++++++++--------------------- 1 file changed, 42 insertions(+), 95 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index bf21f6db20..121104012d 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -42,12 +42,12 @@ describe('withdraw', function () { const allocationRequest = { productId: 0, coverId: 0, - amount: depositToFixture.amount, period: daysToSeconds(10), - }; - - const allocationConfig = { gracePeriod: daysToSeconds(10), + previousStart: 0, + previousExpiration: 0, + previousRewardsRatio: 5000, + useFixedPrice: false, globalCapacityRatio: 20000, capacityReductionRatio: 0, rewardRatio: 5000, @@ -89,14 +89,9 @@ describe('withdraw', function () { }); it('reverts if trying to withdraw stake locked in governance', async function () { - const { - nxm, - stakingPool, - accounts: { - defaultSender: manager, - members: [user], - }, - } = this; + const { nxm, stakingPool } = this; + const manager = this.accounts.defaultSender; + const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -128,14 +123,8 @@ describe('withdraw', function () { }); it('allows to withdraw only stake', async function () { - const { - nxm, - cover, - stakingPool, - accounts: { - members: [user], - }, - } = this; + const { nxm, cover, stakingPool } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -151,7 +140,7 @@ describe('withdraw', function () { ]); const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); @@ -172,16 +161,9 @@ describe('withdraw', function () { expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); }); - it('transfers nxm stake and rewards from token controller to nft owner', async function () { - const { - nxm, - cover, - stakingPool, - tokenController, - accounts: { - members: [user], - }, - } = this; + it.only('transfers nxm stake and rewards from token controller to nft owner', async function () { + const { nxm, cover, stakingPool, tokenController } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -197,7 +179,7 @@ describe('withdraw', function () { ]); const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); @@ -205,32 +187,26 @@ describe('withdraw', function () { const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); - const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); - expect(TCbalanceBefore).to.be.eq(amount.add(tempRewardsAmount)); + expect(tcBalanceBefore).to.be.eq(amount.add(tempRewardsAmount)); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = deposit.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) .add(deposit.pendingRewards); - expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn).sub(amount)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(amount)); }); it('allows to withdraw only rewards', async function () { - const { - nxm, - cover, - stakingPool, - accounts: { - members: [user], - }, - } = this; + const { nxm, cover, stakingPool } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -261,7 +237,7 @@ describe('withdraw', function () { initialTrancheId: firstActiveTrancheId, newTrancheId: firstActiveTrancheId, }); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); @@ -282,14 +258,8 @@ describe('withdraw', function () { }); it('allows to withdraw stake only if tranche is expired', async function () { - const { - tokenController, - nxm, - stakingPool, - accounts: { - members: [user], - }, - } = this; + const { tokenController, nxm, stakingPool } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -310,7 +280,7 @@ describe('withdraw', function () { const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); await expect( stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), @@ -318,24 +288,17 @@ describe('withdraw', function () { const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); - const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); // Nothing changes expect(depositBefore.stakeShares).to.be.eq(depositAfter.stakeShares); expect(userBalanceBefore).to.be.eq(userBalanceAfter); - expect(TCbalanceBefore).to.be.eq(TCbalanceAfter); + expect(tcBalanceBefore).to.be.eq(tcBalanceAfter); }); it('allows to withdraw stake and rewards from multiple tranches', async function () { - const { - nxm, - cover, - stakingPool, - tokenController, - accounts: { - members: [user], - }, - } = this; + const { nxm, cover, stakingPool, tokenController } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; const TRANCHES_NUMBER = 5; @@ -358,7 +321,7 @@ describe('withdraw', function () { ]); trancheIds.push(currentTranche); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); } @@ -369,14 +332,14 @@ describe('withdraw', function () { } const userBalanceBefore = await nxm.balanceOf(user.address); - const TCbalanceBefore = await nxm.balanceOf(tokenController.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; const depositAfter = await stakingPool.deposits(depositNftId, lastTranche); const userBalanceAfter = await nxm.balanceOf(user.address); - const TCbalanceAfter = await nxm.balanceOf(tokenController.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); expect(depositAfter.rewardsShares).to.be.eq(0); @@ -394,17 +357,12 @@ describe('withdraw', function () { } expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn).add(stakeWithdrawn)); - expect(TCbalanceAfter).to.be.eq(TCbalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); }); it('update tranches', async function () { - const { - cover, - stakingPool, - accounts: { - members: [user], - }, - } = this; + const { cover, stakingPool } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -431,7 +389,7 @@ describe('withdraw', function () { const stakeSharesSupplyBefore = await stakingPool.stakeSharesSupply(); const rewardsSharesSupplyBefore = await stakingPool.rewardsSharesSupply(); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); @@ -452,15 +410,9 @@ describe('withdraw', function () { }); it('anyone can call to withdraw stake and rewards for a token id', async function () { - const { - cover, - stakingPool, - nxm, - accounts: { - members: [user], - nonMembers: [randomUser], - }, - } = this; + const { cover, stakingPool, nxm } = this; + const [user] = this.accounts.members; + const [randomUser] = this.accounts.nonMembers; const { amount, tokenId, destination, depositNftId } = depositToFixture; @@ -481,7 +433,7 @@ describe('withdraw', function () { const withdrawRewards = false; const trancheIds = [firstActiveTrancheId]; - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); await increaseTime(TRANCHE_DURATION); @@ -503,13 +455,8 @@ describe('withdraw', function () { }); it('should emit some event', async function () { - const { - cover, - stakingPool, - accounts: { - members: [user], - }, - } = this; + const { cover, stakingPool } = this; + const [user] = this.accounts.members; const { amount, tokenId, destination, depositNftId } = depositToFixture; const coverSigner = await ethers.getImpersonatedSigner(cover.address); @@ -533,7 +480,7 @@ describe('withdraw', function () { ]); trancheIds.push(currentTranche); - await stakingPool.connect(coverSigner).allocateCapacity(allocationRequest, allocationConfig); + await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); } From 1ee4caaf9814058a8b27bbfe4ac58a115781b45a Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:18:57 -0300 Subject: [PATCH 08/19] clarify tokenid/depositNftId naming --- test/unit/StakingPool/withdraw.js | 102 +++++++++++++++--------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 121104012d..2867d367ae 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -10,7 +10,7 @@ const { calculateStakeAndRewardsWithdrawAmounts, } = require('./helpers'); -describe('withdraw', function () { +describe.only('withdraw', function () { const product0 = { productId: 0, weight: 100, @@ -27,13 +27,14 @@ describe('withdraw', function () { ipfsDescriptionHash: 'Description Hash', }; - const tempRewardsAmount = parseEther('10'); + // FIXME: Amount of NXM to be minted before their use as rewards on withdrawal events + const nxmRewards = parseEther('10'); const depositToFixture = { ...initializeParams, amount: parseEther('100'), trancheId: 0, - tokenId: 0, + tokenId: 1, destination: ethers.constants.AddressZero, depositNftId: 1, }; @@ -85,7 +86,7 @@ describe('withdraw', function () { const internalSigner = await ethers.getImpersonatedSigner(internal.address); - await tokenController.connect(internalSigner).mintStakingPoolNXMRewards(tempRewardsAmount, initializeParams.poolId); + await tokenController.connect(internalSigner).mintStakingPoolNXMRewards(nxmRewards, initializeParams.poolId); }); it('reverts if trying to withdraw stake locked in governance', async function () { @@ -101,7 +102,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position, destination, }, ]); @@ -126,7 +127,7 @@ describe('withdraw', function () { const { nxm, cover, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -134,7 +135,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position destination, }, ]); @@ -148,12 +149,12 @@ describe('withdraw', function () { const withdrawRewards = false; const trancheIds = [firstActiveTrancheId]; - const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); - const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); expect(depositBefore.stakeShares).to.be.eq(Math.sqrt(amount)); @@ -161,11 +162,11 @@ describe('withdraw', function () { expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); }); - it.only('transfers nxm stake and rewards from token controller to nft owner', async function () { + it('transfers nxm stake and rewards from token controller to nft owner', async function () { const { nxm, cover, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -173,7 +174,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position, destination, }, ]); @@ -182,19 +183,20 @@ describe('withdraw', function () { await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); const withdrawStake = true; const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); - expect(tcBalanceBefore).to.be.eq(amount.add(tempRewardsAmount)); + expect(tcBalanceBefore).to.be.eq(amount.add(nxmRewards)); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = deposit.rewardsShares @@ -208,7 +210,7 @@ describe('withdraw', function () { const { nxm, cover, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -216,7 +218,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position destination, }, ]); @@ -227,7 +229,7 @@ describe('withdraw', function () { const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; - const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); const rewardShares = await getNewRewardShares({ @@ -241,9 +243,9 @@ describe('withdraw', function () { await increaseTime(TRANCHE_DURATION); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); - const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); expect(depositBefore.rewardsShares).to.be.eq(rewardShares); @@ -261,7 +263,7 @@ describe('withdraw', function () { const { tokenController, nxm, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -269,7 +271,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position destination, }, ]); @@ -278,15 +280,14 @@ describe('withdraw', function () { const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; - const depositBefore = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await expect( - stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), - ).to.not.be.reverted; + await expect(stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])).to.not + .be.reverted; - const depositAfter = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); @@ -299,7 +300,7 @@ describe('withdraw', function () { it('allows to withdraw stake and rewards from multiple tranches', async function () { const { nxm, cover, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const TRANCHES_NUMBER = 5; const trancheIds = []; @@ -315,7 +316,7 @@ describe('withdraw', function () { { amount, trancheId: currentTranche, - tokenId: i === 0 ? tokenId : depositNftId, + tokenId: i === 0 ? 0 : tokenId, // Only create new position for the first tranche destination, }, ]); @@ -328,16 +329,16 @@ describe('withdraw', function () { const depositsBeforeWithdraw = {}; for (const tranche of trancheIds) { - depositsBeforeWithdraw[tranche] = await stakingPool.deposits(depositNftId, tranche); + depositsBeforeWithdraw[tranche] = await stakingPool.deposits(tokenId, tranche); } const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; - const depositAfter = await stakingPool.deposits(depositNftId, lastTranche); + const depositAfter = await stakingPool.deposits(tokenId, lastTranche); const userBalanceAfter = await nxm.balanceOf(user.address); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); @@ -364,7 +365,7 @@ describe('withdraw', function () { const { cover, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -372,7 +373,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position, destination, }, ]); @@ -394,7 +395,7 @@ describe('withdraw', function () { await increaseTime(TRANCHE_DURATION); await mineNextBlock(); - await stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const activeStakeAfter = await stakingPool.activeStake(); const accNxmPerRewardsShareAfter = await stakingPool.accNxmPerRewardsShare(); @@ -414,7 +415,7 @@ describe('withdraw', function () { const [user] = this.accounts.members; const [randomUser] = this.accounts.nonMembers; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); @@ -422,7 +423,7 @@ describe('withdraw', function () { { amount, trancheId: firstActiveTrancheId, - tokenId, + tokenId: 0, // new position destination, }, ]); @@ -435,15 +436,14 @@ describe('withdraw', function () { await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); - const deposit = await stakingPool.deposits(depositNftId, firstActiveTrancheId); + const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); await increaseTime(TRANCHE_DURATION); const userBalanceBefore = await nxm.balanceOf(user.address); const randomUserBalanceBefore = await nxm.balanceOf(randomUser.address); - await expect( - stakingPool.connect(randomUser).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), - ).to.not.be.reverted; + await expect(stakingPool.connect(randomUser).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])).to + .not.be.reverted; const { stake } = await calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, firstActiveTrancheId); @@ -458,7 +458,7 @@ describe('withdraw', function () { const { cover, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination, depositNftId } = depositToFixture; + const { amount, tokenId, destination } = depositToFixture; const coverSigner = await ethers.getImpersonatedSigner(cover.address); const TRANCHES_NUMBER = 3; @@ -474,7 +474,7 @@ describe('withdraw', function () { { amount: amount.mul(i * 5 + 1), trancheId: currentTranche, - tokenId: i === 0 ? tokenId : depositNftId, + tokenId: i === 0 ? 0 : tokenId, // Only create new position for the first tranche destination, }, ]); @@ -487,11 +487,11 @@ describe('withdraw', function () { const depositsBeforeWithdraw = {}; for (const tranche of trancheIds) { - depositsBeforeWithdraw[tranche] = await stakingPool.deposits(depositNftId, tranche); + depositsBeforeWithdraw[tranche] = await stakingPool.deposits(tokenId, tranche); } // Update expiredTranches - await stakingPool.updateTranches(true); + await stakingPool.processExpirations(true); const stakes = []; const rewards = []; @@ -506,14 +506,12 @@ describe('withdraw', function () { rewards.push(currentReward); } - await expect( - stakingPool.connect(user).withdraw([{ tokenId: depositNftId, withdrawStake, withdrawRewards, trancheIds }]), - ) + await expect(stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])) .to.emit(stakingPool, 'Withdraw') - .withArgs(user.address, depositNftId, trancheIds[0], stakes[0], rewards[0]) + .withArgs(user.address, tokenId, trancheIds[0], stakes[0], rewards[0]) .to.emit(stakingPool, 'Withdraw') - .withArgs(user.address, depositNftId, trancheIds[1], stakes[1], rewards[1]) + .withArgs(user.address, tokenId, trancheIds[1], stakes[1], rewards[1]) .to.emit(stakingPool, 'Withdraw') - .withArgs(user.address, depositNftId, trancheIds[2], stakes[2], rewards[2]); + .withArgs(user.address, tokenId, trancheIds[2], stakes[2], rewards[2]); }); }); From ae37e31e16f1ffb117d570e6dd76b69359518887 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:58:14 -0300 Subject: [PATCH 09/19] remove unnecesary expect, mint on beforeEach and improve code reuse with helpers --- test/unit/StakingPool/setup.js | 5 +++ test/unit/StakingPool/withdraw.js | 68 +++++++++++-------------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/test/unit/StakingPool/setup.js b/test/unit/StakingPool/setup.js index c264a22bf4..880182c6ca 100644 --- a/test/unit/StakingPool/setup.js +++ b/test/unit/StakingPool/setup.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { parseEther } = ethers.utils; const { getAccounts } = require('../../utils/accounts'); +const { setEtherBalance } = require('../../utils/evm'); const { Role } = require('../utils').constants; const { zeroPadRight } = require('../utils').helpers; @@ -98,11 +99,15 @@ async function setup() { GLOBAL_MIN_PRICE_RATIO: await cover.GLOBAL_MIN_PRICE_RATIO(), }; + const coverSigner = await ethers.getImpersonatedSigner(cover.address); + await setEtherBalance(coverSigner.address, ethers.utils.parseEther('1')); + this.tokenController = tokenController; this.master = master; this.nxm = nxm; this.stakingPool = stakingPool; this.cover = cover; + this.coverSigner = coverSigner; this.dai = dai; this.accounts = accounts; this.config = config; diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 2867d367ae..48bbcff804 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -2,15 +2,17 @@ const { BigNumber } = require('ethers'); const { parseEther } = require('ethers/lib/utils'); const { ethers, expect } = require('hardhat'); const { daysToSeconds } = require('../../../lib/helpers'); -const { setEtherBalance, increaseTime, mineNextBlock } = require('../../utils/evm'); +const { increaseTime, mineNextBlock } = require('../../utils/evm'); const { getTranches, TRANCHE_DURATION, getNewRewardShares, calculateStakeAndRewardsWithdrawAmounts, + setTime, + generateRewards, } = require('./helpers'); -describe.only('withdraw', function () { +describe('withdraw', function () { const product0 = { productId: 0, weight: 100, @@ -27,16 +29,12 @@ describe.only('withdraw', function () { ipfsDescriptionHash: 'Description Hash', }; - // FIXME: Amount of NXM to be minted before their use as rewards on withdrawal events - const nxmRewards = parseEther('10'); - const depositToFixture = { ...initializeParams, amount: parseEther('100'), trancheId: 0, tokenId: 1, destination: ethers.constants.AddressZero, - depositNftId: 1, }; // Rewards allocation @@ -58,20 +56,13 @@ describe.only('withdraw', function () { beforeEach(async function () { const { stakingPool, - cover, - tokenController, - accounts: { - defaultSender: manager, - internalContracts: [internal], - }, + coverSigner, + accounts: { defaultSender: manager }, } = this; const { poolId, initialPoolFee, maxPoolFee, productInitializationParams, isPrivatePool, ipfsDescriptionHash } = initializeParams; - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await setEtherBalance(coverSigner.address, ethers.utils.parseEther('1')); - await stakingPool .connect(coverSigner) .initialize( @@ -84,9 +75,9 @@ describe.only('withdraw', function () { ipfsDescriptionHash, ); - const internalSigner = await ethers.getImpersonatedSigner(internal.address); - - await tokenController.connect(internalSigner).mintStakingPoolNXMRewards(nxmRewards, initializeParams.poolId); + // Move to the beginning of the next tranche + const { firstActiveTrancheId: trancheId } = await getTranches(); + await setTime((trancheId + 1) * TRANCHE_DURATION); }); it('reverts if trying to withdraw stake locked in governance', async function () { @@ -124,7 +115,7 @@ describe.only('withdraw', function () { }); it('allows to withdraw only stake', async function () { - const { nxm, cover, stakingPool } = this; + const { nxm, coverSigner, stakingPool } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -140,8 +131,7 @@ describe.only('withdraw', function () { }, ]); - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); @@ -163,7 +153,7 @@ describe.only('withdraw', function () { }); it('transfers nxm stake and rewards from token controller to nft owner', async function () { - const { nxm, cover, stakingPool, tokenController } = this; + const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -179,8 +169,7 @@ describe.only('withdraw', function () { }, ]); - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); @@ -190,14 +179,13 @@ describe.only('withdraw', function () { const trancheIds = [firstActiveTrancheId]; const tcBalanceBefore = await nxm.balanceOf(tokenController.address); + const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); - expect(tcBalanceBefore).to.be.eq(amount.add(nxmRewards)); - const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = deposit.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) @@ -207,7 +195,7 @@ describe.only('withdraw', function () { }); it('allows to withdraw only rewards', async function () { - const { nxm, cover, stakingPool } = this; + const { nxm, coverSigner, stakingPool } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -223,8 +211,6 @@ describe.only('withdraw', function () { }, ]); - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - const withdrawStake = false; const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; @@ -239,7 +225,7 @@ describe.only('withdraw', function () { initialTrancheId: firstActiveTrancheId, newTrancheId: firstActiveTrancheId, }); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); @@ -298,7 +284,7 @@ describe.only('withdraw', function () { }); it('allows to withdraw stake and rewards from multiple tranches', async function () { - const { nxm, cover, stakingPool, tokenController } = this; + const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -308,7 +294,6 @@ describe.only('withdraw', function () { const withdrawStake = true; const withdrawRewards = true; - const coverSigner = await ethers.getImpersonatedSigner(cover.address); for (let i = 0; i < TRANCHES_NUMBER; i++) { const { firstActiveTrancheId: currentTranche } = await getTranches(); @@ -322,7 +307,7 @@ describe.only('withdraw', function () { ]); trancheIds.push(currentTranche); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); } @@ -362,7 +347,7 @@ describe.only('withdraw', function () { }); it('update tranches', async function () { - const { cover, stakingPool } = this; + const { coverSigner, stakingPool } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; @@ -378,8 +363,6 @@ describe.only('withdraw', function () { }, ]); - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - const withdrawStake = false; const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; @@ -390,7 +373,7 @@ describe.only('withdraw', function () { const stakeSharesSupplyBefore = await stakingPool.stakeSharesSupply(); const rewardsSharesSupplyBefore = await stakingPool.rewardsSharesSupply(); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); @@ -411,7 +394,7 @@ describe.only('withdraw', function () { }); it('anyone can call to withdraw stake and rewards for a token id', async function () { - const { cover, stakingPool, nxm } = this; + const { coverSigner, stakingPool, nxm } = this; const [user] = this.accounts.members; const [randomUser] = this.accounts.nonMembers; @@ -428,13 +411,11 @@ describe.only('withdraw', function () { }, ]); - const coverSigner = await ethers.getImpersonatedSigner(cover.address); - const withdrawStake = true; const withdrawRewards = false; const trancheIds = [firstActiveTrancheId]; - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); await increaseTime(TRANCHE_DURATION); @@ -455,11 +436,10 @@ describe.only('withdraw', function () { }); it('should emit some event', async function () { - const { cover, stakingPool } = this; + const { coverSigner, stakingPool } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; - const coverSigner = await ethers.getImpersonatedSigner(cover.address); const TRANCHES_NUMBER = 3; const trancheIds = []; @@ -480,7 +460,7 @@ describe.only('withdraw', function () { ]); trancheIds.push(currentTranche); - await stakingPool.connect(coverSigner).requestAllocation(amount, 0, allocationRequest); + await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); await mineNextBlock(); } From 06e873d404488eb069b6c70e3ce1ea9f8bdb81d3 Mon Sep 17 00:00:00 2001 From: Paterson <86971279+paterson1@users.noreply.github.com> Date: Mon, 5 Dec 2022 18:01:27 -0300 Subject: [PATCH 10/19] lint --- test/unit/StakingPool/withdraw.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 48bbcff804..5988896a02 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -1,7 +1,6 @@ const { BigNumber } = require('ethers'); const { parseEther } = require('ethers/lib/utils'); const { ethers, expect } = require('hardhat'); -const { daysToSeconds } = require('../../../lib/helpers'); const { increaseTime, mineNextBlock } = require('../../utils/evm'); const { getTranches, @@ -37,22 +36,6 @@ describe('withdraw', function () { destination: ethers.constants.AddressZero, }; - // Rewards allocation - const allocationRequest = { - productId: 0, - coverId: 0, - period: daysToSeconds(10), - gracePeriod: daysToSeconds(10), - previousStart: 0, - previousExpiration: 0, - previousRewardsRatio: 5000, - useFixedPrice: false, - globalCapacityRatio: 20000, - capacityReductionRatio: 0, - rewardRatio: 5000, - globalMinPrice: 10000, - }; - beforeEach(async function () { const { stakingPool, From 591290d213fafd15c87e4705af92f00386fc5d17 Mon Sep 17 00:00:00 2001 From: shark0der Date: Wed, 7 Dec 2022 18:57:25 +0200 Subject: [PATCH 11/19] Add failing test for withdraw rewards --- test/unit/StakingPool/withdraw.js | 69 ++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 5988896a02..60bbb3290e 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -11,7 +11,7 @@ const { generateRewards, } = require('./helpers'); -describe('withdraw', function () { +describe.only('withdraw', function () { const product0 = { productId: 0, weight: 100, @@ -98,16 +98,15 @@ describe('withdraw', function () { }); it('allows to withdraw only stake', async function () { - const { nxm, coverSigner, stakingPool } = this; + const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; - + const { amount: depositAmount, tokenId, destination } = depositToFixture; const { firstActiveTrancheId } = await getTranches(); await stakingPool.connect(user).depositTo([ { - amount, + amount: depositAmount, trancheId: firstActiveTrancheId, tokenId: 0, // new position destination, @@ -115,7 +114,6 @@ describe('withdraw', function () { ]); await generateRewards(stakingPool, coverSigner); - await increaseTime(TRANCHE_DURATION); const withdrawStake = true; @@ -124,15 +122,20 @@ describe('withdraw', function () { const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); - expect(depositBefore.stakeShares).to.be.eq(Math.sqrt(amount)); + const expectedShares = Math.sqrt(depositAmount); + expect(depositBefore.stakeShares).to.be.eq(expectedShares); expect(depositAfter.stakeShares).to.be.eq(0); - expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(amount)); + + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(depositAmount)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(depositAmount)); }); it('transfers nxm stake and rewards from token controller to nft owner', async function () { @@ -162,12 +165,13 @@ describe('withdraw', function () { const trancheIds = [firstActiveTrancheId]; const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - + const userBalanceBefore = await nxm.balanceOf(user.address); const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); + const userBalanceAfter = await nxm.balanceOf(user.address); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = deposit.rewardsShares @@ -175,14 +179,14 @@ describe('withdraw', function () { .add(deposit.pendingRewards); expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(amount)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn).add(amount)); }); it('allows to withdraw only rewards', async function () { - const { nxm, coverSigner, stakingPool } = this; + const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; const { amount, tokenId, destination } = depositToFixture; - const { firstActiveTrancheId } = await getTranches(); await stakingPool.connect(user).depositTo([ @@ -194,38 +198,55 @@ describe('withdraw', function () { }, ]); - const withdrawStake = false; - const withdrawRewards = true; - const trancheIds = [firstActiveTrancheId]; - - const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); - const userBalanceBefore = await nxm.balanceOf(user.address); - - const rewardShares = await getNewRewardShares({ + const expectedStakeShares = Math.sqrt(amount); + const expectedRewardShares = await getNewRewardShares({ stakingPool, initialStakeShares: 0, - stakeSharesIncrease: depositBefore.stakeShares, + stakeSharesIncrease: expectedStakeShares, initialTrancheId: firstActiveTrancheId, newTrancheId: firstActiveTrancheId, }); + + const withdrawStake = false; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + const tcBalanceInitial = await nxm.balanceOf(tokenController.address); await generateRewards(stakingPool, coverSigner); - await increaseTime(TRANCHE_DURATION); + const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); + const userBalanceBefore = await nxm.balanceOf(user.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); + await increaseTime(TRANCHE_DURATION); await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); - expect(depositBefore.rewardsShares).to.be.eq(rewardShares); + expect(depositBefore.stakeShares).to.be.eq(expectedStakeShares); + expect(depositAfter.stakeShares).to.be.eq(expectedStakeShares); + + expect(depositBefore.rewardsShares).to.be.eq(expectedRewardShares); expect(depositAfter.rewardsShares).to.be.eq(0); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); - const rewardsWithdrawn = depositBefore.rewardsShares + const expectedRewardsWithdrawn = depositBefore.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) .add(depositBefore.pendingRewards); - expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn)); + const rewardsMinted = tcBalanceBefore.sub(tcBalanceInitial); + + // TODO: figure out which of the values is wrong + console.log({ + rewardsMinted: rewardsMinted.toString(), + expectedRewardsWithdrawn: expectedRewardsWithdrawn.toString(), + }); + + expect(expectedRewardsWithdrawn).to.be.eq(rewardsMinted); + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(expectedRewardsWithdrawn)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore); }); it('allows to withdraw stake only if tranche is expired', async function () { From 708c85b19faa92ac55abe500c442e2f893cc4324 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 20 Dec 2022 12:15:22 -0300 Subject: [PATCH 12/19] Fix broken tests due to methods signature changes --- test/unit/StakingPool/withdraw.js | 168 +++++++++++++----------------- 1 file changed, 75 insertions(+), 93 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 60bbb3290e..52af230eca 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -28,7 +28,7 @@ describe.only('withdraw', function () { ipfsDescriptionHash: 'Description Hash', }; - const depositToFixture = { + const withdrawFixture = { ...initializeParams, amount: parseEther('100'), trancheId: 0, @@ -68,18 +68,16 @@ describe.only('withdraw', function () { const manager = this.accounts.defaultSender; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position, - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position, + destination, + ); await increaseTime(TRANCHE_DURATION); @@ -91,7 +89,7 @@ describe.only('withdraw', function () { const trancheIds = [firstActiveTrancheId]; await expect( - stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]), + stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds), ).to.be.revertedWith( 'StakingPool: While the pool manager is locked for governance voting only rewards can be withdrawn', ); @@ -101,17 +99,15 @@ describe.only('withdraw', function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount: depositAmount, tokenId, destination } = depositToFixture; + const { amount: depositAmount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount: depositAmount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + depositAmount, + firstActiveTrancheId, + 0, // new position + destination, + ); await generateRewards(stakingPool, coverSigner); await increaseTime(TRANCHE_DURATION); @@ -124,7 +120,7 @@ describe.only('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); @@ -142,18 +138,16 @@ describe.only('withdraw', function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position, - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position, + destination, + ); await generateRewards(stakingPool, coverSigner); @@ -168,7 +162,7 @@ describe.only('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const deposit = await stakingPool.deposits(tokenId, firstActiveTrancheId); - await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); const userBalanceAfter = await nxm.balanceOf(user.address); @@ -186,17 +180,15 @@ describe.only('withdraw', function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position + destination, + ); const expectedStakeShares = Math.sqrt(amount); const expectedRewardShares = await getNewRewardShares({ @@ -219,7 +211,7 @@ describe.only('withdraw', function () { const tcBalanceBefore = await nxm.balanceOf(tokenController.address); await increaseTime(TRANCHE_DURATION); - await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); @@ -253,18 +245,16 @@ describe.only('withdraw', function () { const { tokenController, nxm, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position + destination, + ); const withdrawStake = true; const withdrawRewards = true; @@ -274,8 +264,8 @@ describe.only('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await expect(stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])).to.not - .be.reverted; + await expect(stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds)).to.not.be + .reverted; const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); @@ -290,7 +280,7 @@ describe.only('withdraw', function () { it('allows to withdraw stake and rewards from multiple tranches', async function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const TRANCHES_NUMBER = 5; const trancheIds = []; @@ -301,14 +291,12 @@ describe.only('withdraw', function () { for (let i = 0; i < TRANCHES_NUMBER; i++) { const { firstActiveTrancheId: currentTranche } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: currentTranche, - tokenId: i === 0 ? 0 : tokenId, // Only create new position for the first tranche - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + currentTranche, + i === 0 ? 0 : tokenId, // Only create new position for the first tranche + destination, + ); trancheIds.push(currentTranche); await generateRewards(stakingPool, coverSigner); @@ -324,7 +312,7 @@ describe.only('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); const lastTranche = trancheIds[TRANCHES_NUMBER - 1]; const depositAfter = await stakingPool.deposits(tokenId, lastTranche); @@ -354,18 +342,16 @@ describe.only('withdraw', function () { const { coverSigner, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position, - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position, + destination, + ); const withdrawStake = false; const withdrawRewards = true; @@ -382,7 +368,7 @@ describe.only('withdraw', function () { await increaseTime(TRANCHE_DURATION); await mineNextBlock(); - await stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }]); + await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); const activeStakeAfter = await stakingPool.activeStake(); const accNxmPerRewardsShareAfter = await stakingPool.accNxmPerRewardsShare(); @@ -402,18 +388,16 @@ describe.only('withdraw', function () { const [user] = this.accounts.members; const [randomUser] = this.accounts.nonMembers; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount, - trancheId: firstActiveTrancheId, - tokenId: 0, // new position - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount, + firstActiveTrancheId, + 0, // new position + destination, + ); const withdrawStake = true; const withdrawRewards = false; @@ -427,8 +411,8 @@ describe.only('withdraw', function () { const userBalanceBefore = await nxm.balanceOf(user.address); const randomUserBalanceBefore = await nxm.balanceOf(randomUser.address); - await expect(stakingPool.connect(randomUser).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])).to - .not.be.reverted; + await expect(stakingPool.connect(randomUser).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds)).to.not + .be.reverted; const { stake } = await calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, firstActiveTrancheId); @@ -443,7 +427,7 @@ describe.only('withdraw', function () { const { coverSigner, stakingPool } = this; const [user] = this.accounts.members; - const { amount, tokenId, destination } = depositToFixture; + const { amount, tokenId, destination } = withdrawFixture; const TRANCHES_NUMBER = 3; const trancheIds = []; @@ -454,14 +438,12 @@ describe.only('withdraw', function () { for (let i = 0; i < TRANCHES_NUMBER; i++) { const { firstActiveTrancheId: currentTranche } = await getTranches(); - await stakingPool.connect(user).depositTo([ - { - amount: amount.mul(i * 5 + 1), - trancheId: currentTranche, - tokenId: i === 0 ? 0 : tokenId, // Only create new position for the first tranche - destination, - }, - ]); + await stakingPool.connect(user).depositTo( + amount.mul(i * 5 + 1), + currentTranche, + i === 0 ? 0 : tokenId, // Only create new position for the first tranche + destination, + ); trancheIds.push(currentTranche); await generateRewards(stakingPool, coverSigner); @@ -490,7 +472,7 @@ describe.only('withdraw', function () { rewards.push(currentReward); } - await expect(stakingPool.connect(user).withdraw([{ tokenId, withdrawStake, withdrawRewards, trancheIds }])) + await expect(stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds)) .to.emit(stakingPool, 'Withdraw') .withArgs(user.address, tokenId, trancheIds[0], stakes[0], rewards[0]) .to.emit(stakingPool, 'Withdraw') From 6fe9105cf4e81101338406c46199b7439a6d0b61 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 21 Dec 2022 04:04:34 -0300 Subject: [PATCH 13/19] Increase accNxmPerRewardsShare precision --- contracts/modules/staking/StakingPool.sol | 18 ++++----- test/unit/StakingPool/withdraw.js | 48 ++++++++++++++++------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/contracts/modules/staking/StakingPool.sol b/contracts/modules/staking/StakingPool.sol index 874cbe2668..05fdc251c7 100644 --- a/contracts/modules/staking/StakingPool.sol +++ b/contracts/modules/staking/StakingPool.sol @@ -282,7 +282,7 @@ contract StakingPool is IStakingPool, ERC721 { uint elapsed = bucketStartTime - _lastAccNxmUpdate; uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0 - ? elapsed * _rewardPerSecond / _rewardsSharesSupply + ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply : 0; _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare); @@ -300,7 +300,7 @@ contract StakingPool is IStakingPool, ERC721 { uint trancheEndTime = (_firstActiveTrancheId + 1) * TRANCHE_DURATION; uint elapsed = trancheEndTime - _lastAccNxmUpdate; uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0 - ? elapsed * _rewardPerSecond / _rewardsSharesSupply + ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply : 0; _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare); _lastAccNxmUpdate = trancheEndTime; @@ -334,7 +334,7 @@ contract StakingPool is IStakingPool, ERC721 { if (updateUntilCurrentTimestamp) { uint elapsed = block.timestamp - _lastAccNxmUpdate; uint newAccNxmPerRewardsShare = _rewardsSharesSupply != 0 - ? elapsed * _rewardPerSecond / _rewardsSharesSupply + ? elapsed * _rewardPerSecond * ONE_NXM / _rewardsSharesSupply : 0; _accNxmPerRewardsShare = _accNxmPerRewardsShare.uncheckedAdd(newAccNxmPerRewardsShare); _lastAccNxmUpdate = block.timestamp; @@ -429,7 +429,7 @@ contract StakingPool is IStakingPool, ERC721 { // if we're increasing an existing deposit if (deposit.rewardsShares != 0) { uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(deposit.lastAccNxmPerRewardShare); - deposit.pendingRewards += newEarningsPerShare * deposit.rewardsShares; + deposit.pendingRewards += newEarningsPerShare * deposit.rewardsShares / ONE_NXM; } deposit.stakeShares += newStakeShares; @@ -451,7 +451,7 @@ contract StakingPool is IStakingPool, ERC721 { // calculate rewards until now uint newRewardPerShare = _accNxmPerRewardsShare.uncheckedSub(feeDeposit.lastAccNxmPerRewardShare); - feeDeposit.pendingRewards += newRewardPerShare * feeDeposit.rewardsShares; + feeDeposit.pendingRewards += newRewardPerShare * feeDeposit.rewardsShares / ONE_NXM; feeDeposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare; feeDeposit.rewardsShares += newFeeRewardShares; } @@ -579,7 +579,7 @@ contract StakingPool is IStakingPool, ERC721 { // calculate reward since checkpoint uint newRewardPerShare = accNxmPerRewardShareToUse.uncheckedSub(deposit.lastAccNxmPerRewardShare); - trancheRewardsToWithdraw = newRewardPerShare * deposit.rewardsShares + deposit.pendingRewards; + trancheRewardsToWithdraw = newRewardPerShare * deposit.rewardsShares / ONE_NXM + deposit.pendingRewards; withdrawnRewards += trancheRewardsToWithdraw; // save checkpoint @@ -1132,13 +1132,13 @@ contract StakingPool is IStakingPool, ERC721 { // if there already is a deposit on the new tranche, calculate its pending rewards if (updatedDeposit.lastAccNxmPerRewardShare != 0) { uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(updatedDeposit.lastAccNxmPerRewardShare); - updatedDeposit.pendingRewards += newEarningsPerShare * updatedDeposit.rewardsShares; + updatedDeposit.pendingRewards += newEarningsPerShare * updatedDeposit.rewardsShares / ONE_NXM; } // calculate the rewards for the deposit being extended and move them to the new deposit { uint newEarningsPerShare = _accNxmPerRewardsShare.uncheckedSub(initialDeposit.lastAccNxmPerRewardShare); - updatedDeposit.pendingRewards += newEarningsPerShare * initialDeposit.rewardsShares; + updatedDeposit.pendingRewards += newEarningsPerShare * initialDeposit.rewardsShares / ONE_NXM; updatedDeposit.pendingRewards += initialDeposit.pendingRewards; } @@ -1426,7 +1426,7 @@ contract StakingPool is IStakingPool, ERC721 { // update pending reward and reward shares uint newRewardPerRewardsShare = _accNxmPerRewardsShare.uncheckedSub(feeDeposit.lastAccNxmPerRewardShare); - feeDeposit.pendingRewards += newRewardPerRewardsShare * feeDeposit.rewardsShares; + feeDeposit.pendingRewards += newRewardPerRewardsShare * feeDeposit.rewardsShares / ONE_NXM; feeDeposit.lastAccNxmPerRewardShare = _accNxmPerRewardsShare; // TODO: would using tranche.rewardsShares give a better precision? feeDeposit.rewardsShares = feeDeposit.rewardsShares * newFee / oldFee; diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 52af230eca..51b60b98c4 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -11,7 +11,7 @@ const { generateRewards, } = require('./helpers'); -describe.only('withdraw', function () { +describe('withdraw', function () { const product0 = { productId: 0, weight: 100, @@ -170,6 +170,7 @@ describe.only('withdraw', function () { const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); const rewardsWithdrawn = deposit.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) + .div(parseEther('1')) .add(deposit.pendingRewards); expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(amount)); @@ -179,6 +180,7 @@ describe.only('withdraw', function () { it('allows to withdraw only rewards', async function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user] = this.accounts.members; + const { defaultSender: manager } = this.accounts; const { amount, tokenId, destination } = withdrawFixture; const { firstActiveTrancheId } = await getTranches(); @@ -199,22 +201,35 @@ describe.only('withdraw', function () { newTrancheId: firstActiveTrancheId, }); - const withdrawStake = false; - const withdrawRewards = true; - const trancheIds = [firstActiveTrancheId]; - const tcBalanceInitial = await nxm.balanceOf(tokenController.address); await generateRewards(stakingPool, coverSigner); const depositBefore = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceBefore = await nxm.balanceOf(user.address); + const managerBalanceBefore = await nxm.balanceOf(manager.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); + const managerTokenId = 0; + const managerDepositBefore = await stakingPool.deposits(managerTokenId, firstActiveTrancheId); + await increaseTime(TRANCHE_DURATION); + + const withdrawStake = false; + const withdrawRewards = true; + const trancheIds = [firstActiveTrancheId]; + + // user withdraw await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); + // Manager withdraw + await stakingPool.connect(manager).withdraw(managerTokenId, withdrawStake, withdrawRewards, trancheIds); + + const rewardPerSecondAfter = await stakingPool.rewardPerSecond(); + expect(rewardPerSecondAfter).to.equal(0); + const depositAfter = await stakingPool.deposits(tokenId, firstActiveTrancheId); const userBalanceAfter = await nxm.balanceOf(user.address); + const managerBalanceAfter = await nxm.balanceOf(manager.address); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); expect(depositBefore.stakeShares).to.be.eq(expectedStakeShares); @@ -224,21 +239,26 @@ describe.only('withdraw', function () { expect(depositAfter.rewardsShares).to.be.eq(0); const { accNxmPerRewardShareAtExpiry } = await stakingPool.expiredTranches(firstActiveTrancheId); - const expectedRewardsWithdrawn = depositBefore.rewardsShares + + const expectedUserRewardsWithdrawn = depositBefore.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) + .div(parseEther('1')) .add(depositBefore.pendingRewards); + const expectedManagerRewardsWithdrawn = managerDepositBefore.rewardsShares + .mul(accNxmPerRewardShareAtExpiry.sub(depositBefore.lastAccNxmPerRewardShare)) + .div(parseEther('1')) + .add(managerDepositBefore.pendingRewards); + + const expectedRewardsWithdrawn = expectedUserRewardsWithdrawn.add(expectedManagerRewardsWithdrawn); + const rewardsMinted = tcBalanceBefore.sub(tcBalanceInitial); - // TODO: figure out which of the values is wrong - console.log({ - rewardsMinted: rewardsMinted.toString(), - expectedRewardsWithdrawn: expectedRewardsWithdrawn.toString(), - }); + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(expectedUserRewardsWithdrawn)); + expect(managerBalanceAfter).to.be.eq(managerBalanceBefore.add(expectedManagerRewardsWithdrawn)); - expect(expectedRewardsWithdrawn).to.be.eq(rewardsMinted); - expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(expectedRewardsWithdrawn)); - expect(tcBalanceAfter).to.be.eq(tcBalanceBefore); + expect(tcBalanceAfter).to.be.eq(tcBalanceInitial.add(1)); // add 1 because of dust + expect(expectedRewardsWithdrawn).to.be.eq(rewardsMinted.sub(1)); // sub 1 because of dust }); it('allows to withdraw stake only if tranche is expired', async function () { From b997ac7fe30ab981545cdb43d3f0aa39de5048eb Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 21 Dec 2022 04:04:48 -0300 Subject: [PATCH 14/19] Fix broken tests --- test/unit/StakingPool/depositTo.js | 12 ++++++------ test/unit/StakingPool/extendDeposit.js | 12 ++++++------ test/unit/StakingPool/helpers.js | 1 + test/unit/StakingPool/setPoolFee.js | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/test/unit/StakingPool/depositTo.js b/test/unit/StakingPool/depositTo.js index d30c816614..f8e5c1ce52 100644 --- a/test/unit/StakingPool/depositTo.js +++ b/test/unit/StakingPool/depositTo.js @@ -258,9 +258,9 @@ describe('depositTo', function () { expect(secondDepositData.lastAccNxmPerRewardShare).to.equal(secondAccNxmPerRewardsShare); expect(secondDepositData.pendingRewards).to.not.equal(0); expect(secondDepositData.pendingRewards).to.equal( - depositData.rewardsShares.mul( - secondDepositData.lastAccNxmPerRewardShare.sub(depositData.lastAccNxmPerRewardShare), - ), + depositData.rewardsShares + .mul(secondDepositData.lastAccNxmPerRewardShare.sub(depositData.lastAccNxmPerRewardShare)) + .div(parseEther('1')), ); // Last deposit @@ -273,9 +273,9 @@ describe('depositTo', function () { expect(lastDepositData.pendingRewards).to.not.equal(0); expect(lastDepositData.pendingRewards).to.equal( secondDepositData.pendingRewards.add( - secondDepositData.rewardsShares.mul( - lastDepositData.lastAccNxmPerRewardShare.sub(secondDepositData.lastAccNxmPerRewardShare), - ), + secondDepositData.rewardsShares + .mul(lastDepositData.lastAccNxmPerRewardShare.sub(secondDepositData.lastAccNxmPerRewardShare)) + .div(parseEther('1')), ), ); }); diff --git a/test/unit/StakingPool/extendDeposit.js b/test/unit/StakingPool/extendDeposit.js index 4aee9c7273..a8d1f57cb1 100644 --- a/test/unit/StakingPool/extendDeposit.js +++ b/test/unit/StakingPool/extendDeposit.js @@ -200,9 +200,9 @@ describe('extendDeposit', function () { expect(newTrancheDeposit.stakeShares).to.equal(initialDeposit.stakeShares); expect(newTrancheDeposit.rewardsShares).to.equal(initialDeposit.rewardsShares.add(newRewardsIncrease)); expect(newTrancheDeposit.pendingRewards).to.equal( - initialDeposit.rewardsShares.mul( - newTrancheDeposit.lastAccNxmPerRewardShare.sub(initialDeposit.lastAccNxmPerRewardShare), - ), + initialDeposit.rewardsShares + .mul(newTrancheDeposit.lastAccNxmPerRewardShare.sub(initialDeposit.lastAccNxmPerRewardShare)) + .div(parseEther('1')), ); expect(newTrancheDeposit.lastAccNxmPerRewardShare).to.equal(accNxmPerRewardsShare); }); @@ -239,9 +239,9 @@ describe('extendDeposit', function () { ); expect(updatedDeposit.rewardsShares).to.equal(initialDeposit.rewardsShares.add(newRewardsIncrease)); expect(updatedDeposit.pendingRewards).to.equal( - initialDeposit.rewardsShares.mul( - updatedDeposit.lastAccNxmPerRewardShare.sub(initialDeposit.lastAccNxmPerRewardShare), - ), + initialDeposit.rewardsShares + .mul(updatedDeposit.lastAccNxmPerRewardShare.sub(initialDeposit.lastAccNxmPerRewardShare)) + .div(parseEther('1')), ); expect(updatedDeposit.lastAccNxmPerRewardShare).to.equal(accNxmPerRewardsShare); }); diff --git a/test/unit/StakingPool/helpers.js b/test/unit/StakingPool/helpers.js index 6be0b4b72e..0ec2731959 100644 --- a/test/unit/StakingPool/helpers.js +++ b/test/unit/StakingPool/helpers.js @@ -197,6 +197,7 @@ async function calculateStakeAndRewardsWithdrawAmounts(stakingPool, deposit, tra return { rewards: deposit.rewardsShares .mul(accNxmPerRewardShareAtExpiry.sub(deposit.lastAccNxmPerRewardShare)) + .div(parseEther('1')) .add(deposit.pendingRewards), stake: stakeAmountAtExpiry.mul(deposit.stakeShares).div(stakeShareSupplyAtExpiry), }; diff --git a/test/unit/StakingPool/setPoolFee.js b/test/unit/StakingPool/setPoolFee.js index 3bdc9eb7d7..f99c34d040 100644 --- a/test/unit/StakingPool/setPoolFee.js +++ b/test/unit/StakingPool/setPoolFee.js @@ -146,7 +146,7 @@ describe('setPoolFee', function () { expect(managerDepositAfter.lastAccNxmPerRewardShare).to.equal(newLastAccNxmPerRewardShare); expect(managerDepositAfter.pendingRewards).to.equal( - managerDepositAfter.lastAccNxmPerRewardShare.mul(managerDepositBefore.rewardsShares), + managerDepositAfter.lastAccNxmPerRewardShare.mul(managerDepositBefore.rewardsShares).div(parseEther('1')), ); expect(managerDepositAfter.rewardsShares).to.equal( managerDepositBefore.rewardsShares.mul(newPoolFee).div(initialPoolFee), From 36aa24717b3c8cf574a2f6401b4d17db7783e02b Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 21 Dec 2022 04:27:00 -0300 Subject: [PATCH 15/19] fix test comments --- test/unit/StakingPool/withdraw.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 51b60b98c4..eefffae05a 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -218,7 +218,7 @@ describe('withdraw', function () { const withdrawRewards = true; const trancheIds = [firstActiveTrancheId]; - // user withdraw + // User withdraw await stakingPool.connect(user).withdraw(tokenId, withdrawStake, withdrawRewards, trancheIds); // Manager withdraw @@ -251,14 +251,12 @@ describe('withdraw', function () { .add(managerDepositBefore.pendingRewards); const expectedRewardsWithdrawn = expectedUserRewardsWithdrawn.add(expectedManagerRewardsWithdrawn); - const rewardsMinted = tcBalanceBefore.sub(tcBalanceInitial); expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(expectedUserRewardsWithdrawn)); expect(managerBalanceAfter).to.be.eq(managerBalanceBefore.add(expectedManagerRewardsWithdrawn)); - - expect(tcBalanceAfter).to.be.eq(tcBalanceInitial.add(1)); // add 1 because of dust - expect(expectedRewardsWithdrawn).to.be.eq(rewardsMinted.sub(1)); // sub 1 because of dust + expect(tcBalanceAfter).to.be.eq(tcBalanceInitial.add(1)); // add 1 because of round error + expect(expectedRewardsWithdrawn).to.be.eq(rewardsMinted.sub(1)); // sub 1 because of round error }); it('allows to withdraw stake only if tranche is expired', async function () { From 9f92780da1fe71448260504d32e12d0c4cbeedc2 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jan 2023 12:08:41 -0300 Subject: [PATCH 16/19] Add multiple users withdraw unit test --- test/unit/StakingPool/withdraw.js | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index eefffae05a..86601959d3 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -498,4 +498,75 @@ describe('withdraw', function () { .to.emit(stakingPool, 'Withdraw') .withArgs(user.address, tokenId, trancheIds[2], stakes[2], rewards[2]); }); + + it('allow multiple users to withdraw stake and rewards from multiple tranches', async function () { + const { nxm, coverSigner, stakingPool, tokenController } = this; + const [user1, user2, user3] = this.accounts.members; + const { amount, destination } = withdrawFixture; + + const users = [user1, user2, user3]; + const tokenIds = [1, 2, 3]; + const TRANCHES_NUMBER = 5; + const trancheIds = []; + + const withdrawStake = true; + const withdrawRewards = true; + + for (let i = 0; i < TRANCHES_NUMBER; i++) { + const { firstActiveTrancheId: currentTranche } = await getTranches(); + + for (let j = 0; j < users.length; j++) { + const user = users[j]; + + await stakingPool.connect(user).depositTo( + amount, + currentTranche, + i === 0 ? 0 : tokenIds[j], // Only create new position for the first tranche + destination, + ); + } + + trancheIds.push(currentTranche); + await generateRewards(stakingPool, coverSigner); + await increaseTime(TRANCHE_DURATION); + await mineNextBlock(); + } + + const depositsBeforeWithdraw = {}; + for (const tranche of trancheIds) { + depositsBeforeWithdraw[tranche] = {}; + + for (let i = 0; i < users.length; i++) { + depositsBeforeWithdraw[tranche][i] = await stakingPool.deposits(tokenIds[i], tranche); + } + } + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + const userBalanceBefore = await nxm.balanceOf(user.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); + + await stakingPool.connect(user).withdraw(tokenIds[i], withdrawStake, withdrawRewards, trancheIds); + + const userBalanceAfter = await nxm.balanceOf(user.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); + + let rewardsWithdrawn = BigNumber.from(0); + let stakeWithdrawn = BigNumber.from(0); + for (const tranche of trancheIds) { + const { rewards, stake } = await calculateStakeAndRewardsWithdrawAmounts( + stakingPool, + depositsBeforeWithdraw[tranche][i], + tranche, + ); + + rewardsWithdrawn = rewardsWithdrawn.add(rewards); + stakeWithdrawn = stakeWithdrawn.add(stake); + } + + expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn).add(stakeWithdrawn)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); + } + }); }); From ef7b8462a7f66947b6765ec7ea9458e6e1909241 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 3 Jan 2023 12:08:51 -0300 Subject: [PATCH 17/19] fix broken depositTo test --- test/unit/StakingPool/depositTo.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/unit/StakingPool/depositTo.js b/test/unit/StakingPool/depositTo.js index f8e5c1ce52..bc5f57cf04 100644 --- a/test/unit/StakingPool/depositTo.js +++ b/test/unit/StakingPool/depositTo.js @@ -1,8 +1,15 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { getTranches, getNewRewardShares, estimateStakeShares, TRANCHE_DURATION } = require('./helpers'); -const { setEtherBalance, increaseTime, setNextBlockTime, mineNextBlock } = require('../utils').evm; +const { + getTranches, + getNewRewardShares, + estimateStakeShares, + POOL_FEE_DENOMINATOR, + setTime, + TRANCHE_DURATION, +} = require('./helpers'); +const { setEtherBalance, increaseTime } = require('../utils').evm; const { daysToSeconds } = require('../utils').helpers; const { BigNumber } = ethers; @@ -56,8 +63,7 @@ describe('depositTo', function () { // Move to the beginning of the next tranche const { firstActiveTrancheId: trancheId } = await getTranches(); - await setNextBlockTime((trancheId + 1) * TRANCHE_DURATION); - await mineNextBlock(); + await setTime((trancheId + 1) * TRANCHE_DURATION); }); it('reverts if caller is not cover contract or manager when pool is private', async function () { From 4c9c806f3d95e025e1d451714ac20d26efad244e Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 4 Jan 2023 16:49:23 -0300 Subject: [PATCH 18/19] Add different deposit amounts for users --- test/unit/StakingPool/helpers.js | 9 +- test/unit/StakingPool/withdraw.js | 190 +++++++++++++++++++++--------- 2 files changed, 143 insertions(+), 56 deletions(-) diff --git a/test/unit/StakingPool/helpers.js b/test/unit/StakingPool/helpers.js index 0ec2731959..8d2eb4cc6c 100644 --- a/test/unit/StakingPool/helpers.js +++ b/test/unit/StakingPool/helpers.js @@ -170,8 +170,13 @@ async function getNewRewardShares(params) { ); } -async function generateRewards(stakingPool, signer, period = daysToSeconds(10), gracePeriod = daysToSeconds(10)) { - const amount = parseEther('1'); +async function generateRewards( + stakingPool, + signer, + period = daysToSeconds(10), + gracePeriod = daysToSeconds(10), + amount = parseEther('1'), +) { const previousPremium = 0; const allocationRequest = { productId: 0, diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 86601959d3..3044230c2e 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -1,47 +1,46 @@ -const { BigNumber } = require('ethers'); -const { parseEther } = require('ethers/lib/utils'); const { ethers, expect } = require('hardhat'); -const { increaseTime, mineNextBlock } = require('../../utils/evm'); +const { increaseTime, mineNextBlock } = require('../utils').evm; const { getTranches, - TRANCHE_DURATION, getNewRewardShares, + estimateStakeShares, calculateStakeAndRewardsWithdrawAmounts, setTime, generateRewards, + TRANCHE_DURATION, } = require('./helpers'); -describe('withdraw', function () { - const product0 = { - productId: 0, - weight: 100, - initialPrice: '500', - targetPrice: '500', - }; - - const initializeParams = { - poolId: 0, - isPrivatePool: false, - initialPoolFee: 5, // 5% - maxPoolFee: 5, // 5% - productInitializationParams: [product0], - ipfsDescriptionHash: 'Description Hash', - }; - - const withdrawFixture = { - ...initializeParams, - amount: parseEther('100'), - trancheId: 0, - tokenId: 1, - destination: ethers.constants.AddressZero, - }; +const { BigNumber } = ethers; +const { parseEther } = ethers.utils; + +const product0 = { + productId: 0, + weight: 100, + initialPrice: '500', + targetPrice: '500', +}; + +const initializeParams = { + poolId: 0, + isPrivatePool: false, + initialPoolFee: 5, // 5% + maxPoolFee: 5, // 5% + productInitializationParams: [product0], + ipfsDescriptionHash: 'Description Hash', +}; + +const withdrawFixture = { + ...initializeParams, + amount: parseEther('100'), + trancheId: 0, + tokenId: 1, + destination: ethers.constants.AddressZero, +}; +describe('withdraw', function () { beforeEach(async function () { - const { - stakingPool, - coverSigner, - accounts: { defaultSender: manager }, - } = this; + const { stakingPool, coverSigner } = this; + const manager = this.accounts.defaultSender; const { poolId, initialPoolFee, maxPoolFee, productInitializationParams, isPrivatePool, ipfsDescriptionHash } = initializeParams; @@ -502,71 +501,154 @@ describe('withdraw', function () { it('allow multiple users to withdraw stake and rewards from multiple tranches', async function () { const { nxm, coverSigner, stakingPool, tokenController } = this; const [user1, user2, user3] = this.accounts.members; - const { amount, destination } = withdrawFixture; + const { defaultSender: manager } = this.accounts; + const { destination } = withdrawFixture; const users = [user1, user2, user3]; + const depositAmounts = [ + [parseEther('100'), parseEther('300'), parseEther('200')], + [parseEther('150'), parseEther('225'), parseEther('333')], + [parseEther('600'), parseEther('100'), parseEther('100')], + [parseEther('120'), parseEther('75'), parseEther('1')], + [parseEther('13'), parseEther('100'), parseEther('100')], + ]; + const tokenIds = [1, 2, 3]; - const TRANCHES_NUMBER = 5; + const TRANCHE_COUNT = 5; const trancheIds = []; const withdrawStake = true; const withdrawRewards = true; - for (let i = 0; i < TRANCHES_NUMBER; i++) { - const { firstActiveTrancheId: currentTranche } = await getTranches(); + const { firstActiveTrancheId: currentTranche } = await getTranches(); + const userShares = {}; + + let activeStake = BigNumber.from(0); + let stakeSharesSupply = BigNumber.from(0); - for (let j = 0; j < users.length; j++) { - const user = users[j]; + for (let t = 0; t < TRANCHE_COUNT; t++) { + userShares[t] = {}; + + for (let uid = 0; uid < users.length; uid++) { + const user = users[uid]; + const amount = depositAmounts[t][uid]; + + const stakeShares = await estimateStakeShares({ amount, stakingPool }); + userShares[t][uid] = { amount, stakeShares }; await stakingPool.connect(user).depositTo( amount, - currentTranche, - i === 0 ? 0 : tokenIds[j], // Only create new position for the first tranche + currentTranche + t, + t === 0 ? 0 : tokenIds[uid], // Only create new position for the first tranche destination, ); + + stakeSharesSupply = stakeSharesSupply.add(stakeShares); + activeStake = activeStake.add(amount); } - trancheIds.push(currentTranche); - await generateRewards(stakingPool, coverSigner); - await increaseTime(TRANCHE_DURATION); - await mineNextBlock(); + trancheIds.push(currentTranche + t); } + const tcBalanceBeforeRewards = await nxm.balanceOf(tokenController.address); + + const allocationAmount = parseEther('100'); + await generateRewards(stakingPool, coverSigner, allocationAmount); + + const tcBalanceAfterRewards = await nxm.balanceOf(tokenController.address); + const rewardedAmount = tcBalanceAfterRewards.sub(tcBalanceBeforeRewards); + + await increaseTime(TRANCHE_DURATION * TRANCHE_COUNT); + await mineNextBlock(); + const depositsBeforeWithdraw = {}; - for (const tranche of trancheIds) { + + for (let t = 0; t < TRANCHE_COUNT; t++) { + const tranche = trancheIds[t]; depositsBeforeWithdraw[tranche] = {}; - for (let i = 0; i < users.length; i++) { - depositsBeforeWithdraw[tranche][i] = await stakingPool.deposits(tokenIds[i], tranche); + for (let uid = 0; uid < users.length; uid++) { + const deposit = await stakingPool.deposits(tokenIds[uid], tranche); + depositsBeforeWithdraw[tranche][uid] = deposit; + + const { stakeShares } = deposit; + expect(stakeShares).to.eq(userShares[t][uid].stakeShares); } } - for (let i = 0; i < users.length; i++) { - const user = users[i]; + let totalRewardsWithdrawn = BigNumber.from(0); + + for (let uid = 0; uid < users.length; uid++) { + const user = users[uid]; const userBalanceBefore = await nxm.balanceOf(user.address); const tcBalanceBefore = await nxm.balanceOf(tokenController.address); - await stakingPool.connect(user).withdraw(tokenIds[i], withdrawStake, withdrawRewards, trancheIds); + await stakingPool.connect(user).withdraw(tokenIds[uid], withdrawStake, withdrawRewards, trancheIds); const userBalanceAfter = await nxm.balanceOf(user.address); const tcBalanceAfter = await nxm.balanceOf(tokenController.address); let rewardsWithdrawn = BigNumber.from(0); let stakeWithdrawn = BigNumber.from(0); - for (const tranche of trancheIds) { + + for (let t = 0; t < TRANCHE_COUNT; t++) { + const tranche = trancheIds[t]; + const { rewards, stake } = await calculateStakeAndRewardsWithdrawAmounts( stakingPool, - depositsBeforeWithdraw[tranche][i], + depositsBeforeWithdraw[tranche][uid], tranche, ); - rewardsWithdrawn = rewardsWithdrawn.add(rewards); stakeWithdrawn = stakeWithdrawn.add(stake); + rewardsWithdrawn = rewardsWithdrawn.add(rewards); + totalRewardsWithdrawn = totalRewardsWithdrawn.add(rewards); } expect(userBalanceAfter).to.be.eq(userBalanceBefore.add(rewardsWithdrawn).add(stakeWithdrawn)); expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); } + + // withdraw manager rewards + const managerTokenId = 0; + const managerDepositsBeforeWithdraw = {}; + for (let t = 0; t < TRANCHE_COUNT; t++) { + const tranche = trancheIds[t]; + const deposit = await stakingPool.deposits(managerTokenId, tranche); + managerDepositsBeforeWithdraw[tranche] = deposit; + } + + const managerBalanceBefore = await nxm.balanceOf(manager.address); + const tcBalanceBefore = await nxm.balanceOf(tokenController.address); + + await stakingPool.connect(manager).withdraw(managerTokenId, withdrawStake, withdrawRewards, trancheIds); + + const managerBalanceAfter = await nxm.balanceOf(manager.address); + const tcBalanceAfter = await nxm.balanceOf(tokenController.address); + + let rewardsWithdrawn = BigNumber.from(0); + let stakeWithdrawn = BigNumber.from(0); + + for (let t = 0; t < TRANCHE_COUNT; t++) { + const tranche = trancheIds[t]; + + const { rewards, stake } = await calculateStakeAndRewardsWithdrawAmounts( + stakingPool, + managerDepositsBeforeWithdraw[tranche], + tranche, + ); + + stakeWithdrawn = stakeWithdrawn.add(stake); + rewardsWithdrawn = rewardsWithdrawn.add(rewards); + totalRewardsWithdrawn = totalRewardsWithdrawn.add(rewards); + } + + expect(managerBalanceAfter).to.be.eq(managerBalanceBefore.add(rewardsWithdrawn).add(stakeWithdrawn)); + expect(tcBalanceAfter).to.be.eq(tcBalanceBefore.sub(rewardsWithdrawn).sub(stakeWithdrawn)); + + // Consider 10 wei of accumulated round error + expect(totalRewardsWithdrawn).to.be.gte(rewardedAmount.sub(10)); + expect(totalRewardsWithdrawn).to.be.lte(rewardedAmount); }); }); From 5d510a3a262287c11cbfc6d49074a0935c6bf965 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Thu, 5 Jan 2023 09:51:00 -0300 Subject: [PATCH 19/19] Fix broken tests after rebase --- test/unit/StakingPool/depositTo.js | 9 +-------- test/unit/StakingPool/processExpirations.js | 12 +++++++++--- test/unit/StakingPool/withdraw.js | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/test/unit/StakingPool/depositTo.js b/test/unit/StakingPool/depositTo.js index bc5f57cf04..c5df2acc4c 100644 --- a/test/unit/StakingPool/depositTo.js +++ b/test/unit/StakingPool/depositTo.js @@ -1,14 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { - getTranches, - getNewRewardShares, - estimateStakeShares, - POOL_FEE_DENOMINATOR, - setTime, - TRANCHE_DURATION, -} = require('./helpers'); +const { getTranches, getNewRewardShares, estimateStakeShares, setTime, TRANCHE_DURATION } = require('./helpers'); const { setEtherBalance, increaseTime } = require('../utils').evm; const { daysToSeconds } = require('../utils').helpers; diff --git a/test/unit/StakingPool/processExpirations.js b/test/unit/StakingPool/processExpirations.js index 63496f6d9f..5146922686 100644 --- a/test/unit/StakingPool/processExpirations.js +++ b/test/unit/StakingPool/processExpirations.js @@ -293,11 +293,13 @@ describe('processExpirations', function () { const accFromBeforeToBucketExpiration = nextBucketStartTime .sub(lastAccNxmUpdateBefore) .mul(rewardPerSecondBefore) + .mul(parseEther('1')) .div(rewardsSharesSupply); const accFromBucketExpirationToTrancheExpiration = trancheEndTime .sub(nextBucketStartTime) .mul(rewardPerSecondBefore.sub(nextBucketRewardPerSecondCut)) + .mul(parseEther('1')) .div(rewardsSharesSupply); expect(expiredTranche.accNxmPerRewardShareAtExpiry).to.equal( @@ -314,11 +316,13 @@ describe('processExpirations', function () { const accFromTrancheExpirationToSecondBucketExpiration = secondNextBucketStartTime .sub(trancheEndTime) .mul(rewardPerSecondBefore.sub(nextBucketRewardPerSecondCut)) + .mul(parseEther('1')) .div(rewardsSharesSupply.sub(tranche.rewardsShares)); const accFromSecondBucketExpirationToCurrentTime = BigNumber.from(timestamp) .sub(secondNextBucketStartTime) .mul(rewardPerSecondBefore.sub(nextBucketRewardPerSecondCut).sub(secondBucketRewardPerSecondCut)) + .mul(parseEther('1')) .div(rewardsSharesSupply.sub(tranche.rewardsShares)); expect(accNxmPerRewardsShareAfter).to.equal( @@ -365,7 +369,7 @@ describe('processExpirations', function () { expect(expiredBucketRewards).to.equal(rewardPerSecondBefore); expect(rewardPerSecondAfter).to.equal(rewardPerSecondBefore.sub(expiredBucketRewards)); expect(accNxmPerRewardsShareAfter).to.equal( - accNxmPerRewardsShareBefore.add(elapsed.mul(rewardPerSecondBefore).div(rewardsSharesSupply)), + accNxmPerRewardsShareBefore.add(elapsed.mul(rewardPerSecondBefore).mul(parseEther('1')).div(rewardsSharesSupply)), ); expect(lastAccNxmUpdateAfter).to.equal(bucketStartTime); }); @@ -463,10 +467,12 @@ describe('processExpirations', function () { const elapsedAfterBucket = BigNumber.from(lastBlock.timestamp).sub(lastAccNxmUpdateBefore); const accNxmPerRewardsAtBucketEnd = accNxmPerRewardsShareBefore.add( - elapsedInBucket.mul(rewardPerSecondBefore).div(rewardsSharesSupply), + elapsedInBucket.mul(rewardPerSecondBefore).mul(parseEther('1')).div(rewardsSharesSupply), ); expect(accNxmPerRewardsShareAfter).to.equal( - accNxmPerRewardsAtBucketEnd.add(elapsedAfterBucket.mul(rewardPerSecondAfter).div(rewardsSharesSupply)), + accNxmPerRewardsAtBucketEnd.add( + elapsedAfterBucket.mul(rewardPerSecondAfter).mul(parseEther('1')).div(rewardsSharesSupply), + ), ); expect(lastAccNxmUpdateAfter).to.equal(lastBlock.timestamp); }); diff --git a/test/unit/StakingPool/withdraw.js b/test/unit/StakingPool/withdraw.js index 3044230c2e..e8d12eb746 100644 --- a/test/unit/StakingPool/withdraw.js +++ b/test/unit/StakingPool/withdraw.js @@ -553,7 +553,7 @@ describe('withdraw', function () { const tcBalanceBeforeRewards = await nxm.balanceOf(tokenController.address); const allocationAmount = parseEther('100'); - await generateRewards(stakingPool, coverSigner, allocationAmount); + await generateRewards(stakingPool, coverSigner, undefined, undefined, allocationAmount); const tcBalanceAfterRewards = await nxm.balanceOf(tokenController.address); const rewardedAmount = tcBalanceAfterRewards.sub(tcBalanceBeforeRewards);