difftreelog
Fix test: fix create accounts script for devnode
in: master
- add waiter for create accounts script - wrap promotion tests in global describe to avoid accounts creation for each describe
2 files changed
tests/src/app-promotion.test.tsdiffbeforeafterboth36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain37const rewardAvailableInBlock = (stakedInBlock: bigint) => (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);37const rewardAvailableInBlock = (stakedInBlock: bigint) => (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);383839const beforeEach = async (context: Mocha.Context) => {40 await usingPlaygrounds(async (helper, privateKey) => {41 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) context.skip();42 alice = privateKey('//Alice');43 palletAdmin = privateKey('//Charlie'); // TODO use custom address44 await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));45 nominal = helper.balance.getOneTokenNominal();46 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal);47 await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal);48 accounts = await helper.arrange.createCrowd(100, 1000n, alice); // create accounts-pool to speed up tests49 });50};5152describe('app-promotions.stake extrinsic', () => {39describe('App promotion', () => {53 before(async function () {40 before(async function () {54 await beforeEach(this);41 await usingPlaygrounds(async (helper, privateKey) => {42 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();43 alice = privateKey('//Alice');44 palletAdmin = privateKey('//Charlie'); // TODO use custom address45 await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));46 nominal = helper.balance.getOneTokenNominal();47 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal);48 await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal);49 accounts = await helper.arrange.createCrowd(100, 1000n, alice); // create accounts-pool to speed up tests50 });55 });51 });565257 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {53 describe('stake extrinsic', () => { 58 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];54 itSub('should "lock" staking balance, add it to "staked" map, and increase "totalStaked" amount', async ({helper}) => {59 const totalStakedBefore = await helper.staking.getTotalStaked();55 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];6056 const totalStakedBefore = await helper.staking.getTotalStaked();61 // Minimum stake amount is 100:57 62 await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.rejected;58 // Minimum stake amount is 100:63 await helper.staking.stake(staker, 100n * nominal);59 await expect(helper.staking.stake(staker, 100n * nominal - 1n)).to.be.rejected;6460 await helper.staking.stake(staker, 100n * nominal);65 // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...61 66 // ...so he can not transfer 90062 // Staker balance is: miscFrozen: 100, feeFrozen: 100, reserved: 0n...67 expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});63 // ...so he can not transfer 90068 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');64 expect (await helper.balance.getSubstrateFull(staker.address)).to.contain({miscFrozen: 100n * nominal, feeFrozen: 100n * nominal, reserved: 0n});65 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 900n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions');66 67 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);68 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);69 // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? 70 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased 71 72 73 await helper.staking.stake(staker, 200n * nominal);74 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);75 const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});76 expect(totalStakedPerBlock[0].amount).to.equal(100n * nominal);77 expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);78 });79 80 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {81 const [staker] = await helper.arrange.createAccounts([2000n], alice);82 for (let i = 0; i < 10; i++) {83 await helper.staking.stake(staker, 100n * nominal);84 }85 86 // can have 10 stakes87 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);88 expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);89 90 await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');91 92 // After unstake can stake again93 await helper.staking.unstake(staker);94 await helper.staking.stake(staker, 100n * nominal);95 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);96 });69 97 70 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(100n * nominal);98 itSub('should reject transaction if stake amount is more than total free balance minus frozen', async ({helper}) => {99 const staker = accounts.pop()!;100 101 // Can't stake full balance because Alice needs to pay some fee102 await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected;71 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);103 await helper.staking.stake(staker, 500n * nominal);72 // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? 104 105 // Can't stake 500 tkn because Alice has Less than 500 transferable;106 await expect(helper.staking.stake(staker, 500n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions'); 73 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased 107 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(500n * nominal);74108 });75 109 76 await helper.staking.stake(staker, 200n * nominal);110 itSub('for different accounts in one block is possible', async ({helper}) => {111 const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];112 113 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));77 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal);114 await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;115 78 const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});116 const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));79 expect(totalStakedPerBlock[0].amount).to.equal(100n * nominal);117 expect(crowdStakes).to.deep.equal([100n * nominal, 100n * nominal, 100n * nominal, 100n * nominal]);80 expect(totalStakedPerBlock[1].amount).to.equal(200n * nominal);118 });81 });119 });82120 83 itSub('should allow to create maximum 10 stakes for account', async ({helper}) => {121 describe('unstake extrinsic', () => { 122 itSub('should change balance state from "frozen" to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async ({helper}) => {84 const [staker] = await helper.arrange.createAccounts([2000n], alice);123 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];124 const totalStakedBefore = await helper.staking.getTotalStaked();125 await helper.staking.stake(staker, 900n * nominal);85 for (let i = 0; i < 10; i++) {126 await helper.staking.unstake(staker);127 128 // Right after unstake balance is reserved129 // Staker can not transfer 130 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 900n * nominal, miscFrozen: 0n, feeFrozen: 0n});131 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.InsufficientBalance');132 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);133 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);134 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);135 });136 137 itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {138 const staker = accounts.pop()!;86 await helper.staking.stake(staker, 100n * nominal);139 await helper.staking.stake(staker, 100n * nominal);87 }140 await helper.staking.unstake(staker);8889 // can have 10 stakes90 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(1000n * nominal);91 expect(await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).to.have.length(10);9293 await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');9495 // After unstake can stake again96 await helper.staking.unstake(staker);97 await helper.staking.stake(staker, 100n * nominal);141 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});98 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.equal(100n * nominal);99 });100 142 101 itSub('should reject transaction if stake amount is more than total free balance minus frozen', async ({helper}) => {143 // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n102 const staker = accounts.pop()!;144 await helper.wait.forParachainBlockNumber(pendingUnstake.block);103104 // Can't stake full balance because Alice needs to pay some fee145 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});105 await expect(helper.staking.stake(staker, 1000n * nominal)).to.be.rejected;106 await helper.staking.stake(staker, 500n * nominal);107108 // Can't stake 500 tkn because Alice has Less than 500 transferable;146 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);109 await expect(helper.staking.stake(staker, 500n * nominal)).to.be.rejectedWith('balances.LiquidityRestrictions'); 110 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(500n * nominal);111 });112 147 113 itSub('for different accounts in one block is possible', async ({helper}) => {148 // staker can transfer:114 const crowd = [accounts.pop()!, accounts.pop()!, accounts.pop()!, accounts.pop()!];149 await helper.balance.transferToSubstrate(staker, alice.address, 998n * nominal);115 150 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);116 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, 100n * nominal));151 });117 await expect(Promise.all(crowdStartsToStake)).to.be.fulfilled;152 118153 itSub('should successfully unstake multiple stakes', async ({helper}) => {119 const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));154 const staker = accounts.pop()!;120 expect(crowdStakes).to.deep.equal([100n * nominal, 100n * nominal, 100n * nominal, 100n * nominal]);155 await helper.staking.stake(staker, 100n * nominal);121 });156 await helper.staking.stake(staker, 200n * nominal);122});157 await helper.staking.stake(staker, 300n * nominal);123158 124describe('unstake balance extrinsic', () => { 159 // staked: [100, 200, 300]; unstaked: 0125 before(async function () {160 let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});126 await beforeEach(this);161 let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});127 });162 let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});128163 expect(totalPendingUnstake).to.be.deep.equal(0n);129 itSub('should change balance state from "frozen" to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async ({helper}) => {164 expect(pendingUnstake).to.be.deep.equal([]);130 const [staker, recepient] = [accounts.pop()!, accounts.pop()!];165 expect(stakes[0].amount).to.equal(100n * nominal);131 const totalStakedBefore = await helper.staking.getTotalStaked();166 expect(stakes[1].amount).to.equal(200n * nominal);132 await helper.staking.stake(staker, 900n * nominal);167 expect(stakes[2].amount).to.equal(300n * nominal);133 await helper.staking.unstake(staker);168 134169 // Can unstake multiple stakes135 // Right after unstake balance is reserved170 await helper.staking.unstake(staker);136 // Staker can not transfer 171 pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});137 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 900n * nominal, miscFrozen: 0n, feeFrozen: 0n});172 totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});138 await expect(helper.balance.transferToSubstrate(staker, recepient.address, 100n * nominal)).to.be.rejectedWith('balances.InsufficientBalance');173 stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});139 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(900n * nominal);174 expect(totalPendingUnstake).to.be.equal(600n * nominal);140 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);175 expect(stakes).to.be.deep.equal([]);141 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore);176 expect(pendingUnstake[0].amount).to.equal(600n * nominal);142 });177 143178 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 600n * nominal, feeFrozen: 0n, miscFrozen: 0n});144 itSub('should unlock balance after unlocking period ends and remove it from "pendingUnstake"', async ({helper}) => {179 await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);145 const staker = accounts.pop()!;180 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});146 await helper.staking.stake(staker, 100n * nominal);181 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);147 await helper.staking.unstake(staker);182 });148 const [pendingUnstake] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});183 149184 itSub('should not have any effects if no active stakes', async ({helper}) => {150 // Wait for unstaking period. Balance now free ~1000; reserved, frozen, miscFrozeb: 0n185 const staker = accounts.pop()!;151 await helper.wait.forParachainBlockNumber(pendingUnstake.block);186 152 expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, miscFrozen: 0n, feeFrozen: 0n});187 // unstake has no effect if no stakes at all153 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);188 await helper.staking.unstake(staker);154189 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);155 // staker can transfer:190 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper156 await helper.balance.transferToSubstrate(staker, alice.address, 998n * nominal);191 157 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(1n);192 // TODO stake() unstake() waitUnstaked() unstake();158 });193 159194 // can't unstake if there are only pendingUnstakes160 itSub('should successfully unstake multiple stakes', async ({helper}) => {195 await helper.staking.stake(staker, 100n * nominal);161 const staker = accounts.pop()!;196 await helper.staking.unstake(staker);162 await helper.staking.stake(staker, 100n * nominal);197 await helper.staking.unstake(staker);163 await helper.staking.stake(staker, 200n * nominal);198 164 await helper.staking.stake(staker, 300n * nominal);165166 // staked: [100, 200, 300]; unstaked: 0167 let totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});168 let pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});169 let stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});170 expect(totalPendingUnstake).to.be.deep.equal(0n);171 expect(pendingUnstake).to.be.deep.equal([]);172 expect(stakes[0].amount).to.equal(100n * nominal);173 expect(stakes[1].amount).to.equal(200n * nominal);174 expect(stakes[2].amount).to.equal(300n * nominal);175 176 // Can unstake multiple stakes177 await helper.staking.unstake(staker);178 pendingUnstake = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});179 totalPendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});180 stakes = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});181 expect(totalPendingUnstake).to.be.equal(600n * nominal);182 expect(stakes).to.be.deep.equal([]);183 expect(pendingUnstake[0].amount).to.equal(600n * nominal);184185 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 600n * nominal, feeFrozen: 0n, miscFrozen: 0n});186 await helper.wait.forParachainBlockNumber(pendingUnstake[0].block);187 expect (await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, feeFrozen: 0n, miscFrozen: 0n});188 expect (await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n);189 });190191 itSub('should not have any effects if no active stakes', async ({helper}) => {192 const staker = accounts.pop()!;193 194 // unstake has no effect if no stakes at all195 await helper.staking.unstake(staker);196 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n);197 expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper198199 // TODO stake() unstake() waitUnstaked() unstake();200201 // can't unstake if there are only pendingUnstakes202 await helper.staking.stake(staker, 100n * nominal);203 await helper.staking.unstake(staker);204 await helper.staking.unstake(staker);205206 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);207 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);208 });209210 itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {211 const staker = accounts.pop()!;212 await helper.staking.stake(staker, 100n * nominal);213 await helper.staking.unstake(staker);214 await helper.staking.stake(staker, 120n * nominal);215 await helper.staking.unstake(staker);216217 const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});218 expect(unstakingPerBlock).has.length(2);219 expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);220 expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);221 });222223 itSub('should be possible for different accounts in one block', async ({helper}) => {224 const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];225226 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));227 await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));228229 await Promise.all(stakers.map(async (staker) => {230 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);199 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);231 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);200 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);232 }));201 });233 });234});235236describe('Admin adress', () => {237 before(async function () {238 await beforeEach(this);239 });240241 itSub('can be set by sudo only', async ({helper}) => {242 const nonAdmin = accounts.pop()!;243 // nonAdmin can not set admin not from himself nor as a sudo244 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected;245 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected;246247 // Alice can248 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;249 });250 202 251 itSub('can be any valid CrossAccountId', async ({helper}) => {203 itSub('should keep different unlocking block for each unlocking stake', async ({helper}) => {252 // We are not going to set an eth address as a sponsor,204 const staker = accounts.pop()!;253 // but we do want to check, it doesn't break anything;254 const account = accounts.pop()!;255 const ethAccount = helper.address.substrateToEth(account.address); 205 await helper.staking.stake(staker, 100n * nominal);256 // Alice sets Ethereum address as a sudo. Then Substrate address back...257 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled;258 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;206 await helper.staking.unstake(staker);259 207 await helper.staking.stake(staker, 120n * nominal);260 // ...It doesn't break anything;261 const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});262 await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;208 await helper.staking.unstake(staker);263 });209 264265 itSub('can be reassigned', async ({helper}) => {210 const unstakingPerBlock = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});266 const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];267 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});268 211 expect(unstakingPerBlock).has.length(2);269 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.fulfilled;270 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.fulfilled;212 expect(unstakingPerBlock[0].amount).to.equal(100n * nominal);271 await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;272 213 expect(unstakingPerBlock[1].amount).to.equal(120n * nominal);273 await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;274 });275});276277describe('App-promotion collection sponsoring', () => {278 before(async function () {279 await beforeEach(this);280 await usingPlaygrounds(async (helper) => {281 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}));282 await helper.signTransaction(alice, tx);283 });214 });284 });285286 itSub('should actually sponsor transactions', async ({helper}) => {287 const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];288 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});289 const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});290 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));291 const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);292293 await token.transfer(tokenSender, {Substrate: receiver.address});294 expect (await token.getOwner()).to.be.deep.equal({Substrate: receiver.address});295 const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);296297 // senders balance the same, transaction has sponsored298 expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);299 expect (palletBalanceBefore > palletBalanceAfter).to.be.true;300 });301302 itSub('can not be set by non admin', async ({helper}) => {303 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];304305 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});306 307 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;308 expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled');309 });310311 itSub('should set pallet address as confirmed admin', async ({helper}) => {312 const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];313 314 // Can set sponsoring for collection without sponsor315 const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});316 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled;317 expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});318319 // Can set sponsoring for collection with unconfirmed sponsor320 const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});321 expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address});322 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled;323 expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});324325 // Can set sponsoring for collection with confirmed sponsor326 const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});327 await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor);328 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled;329 expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});330 });331332 itSub('can be overwritten by collection owner', async ({helper}) => { 333 const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];334 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});335 const collectionId = collection.collectionId;336 337 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled;338 339 // Collection limits still can be changed by the owner340 expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true;341 expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);342 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});343344 // Collection sponsor can be changed too345 expect((await collection.setSponsor(collectionOwner, newSponsor.address))).to.be.true;346 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: newSponsor.address});347 });348 215 349 itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {216 itSub('should be possible for different accounts in one block', async ({helper}) => {350 const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};217 const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];351 const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});352353 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;354 expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);355 });356 218 357 itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {219 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));358 const collectionOwner = accounts.pop()!;220 await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));359 221 360 // collection has never existed222 await Promise.all(stakers.map(async (staker) => {361 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;223 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(100n * nominal);362 // collection has been burned363 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});224 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(0n);364 await collection.burn(collectionOwner);225 }));365366 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;226 });367 });227 });368});228 369229 describe('admin adress', () => {370describe('app-promotion stopSponsoringCollection', () => {230 itSub('can be set by sudo only', async ({helper}) => {371 before(async function () {231 const nonAdmin = accounts.pop()!;372 await beforeEach(this);232 // nonAdmin can not set admin not from himself nor as a sudo373 });233 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected;374234 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected;375 itSub('can not be called by non-admin', async ({helper}) => { 235 376 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];236 // Alice can377 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});237 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;378 238 });379 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;380 381 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;382 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});383 });384385 itSub('should set sponsoring as disabled', async ({helper}) => {386 const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];387 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});388 const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});389 390 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));391 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId));392 393 expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');394395 // Transactions are not sponsored anymore:396 const ownerBalanceBefore = await helper.balance.getSubstrate(collectionOwner.address);397 await token.transfer(collectionOwner, {Substrate: recepient.address});398 const ownerBalanceAfter = await helper.balance.getSubstrate(collectionOwner.address);399 expect(ownerBalanceAfter < ownerBalanceBefore).to.be.equal(true);400 });401402 itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {403 const collectionOwner = accounts.pop()!;404 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});405 await collection.confirmSponsorship(collectionOwner);406 407 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;408 409 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address});410 });411412 itSub('should reject transaction if collection does not exist', async ({helper}) => { 413 const collectionOwner = accounts.pop()!;414 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});415 416 await collection.burn(collectionOwner);417 await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [collection.collectionId], true)).to.be.rejectedWith('common.CollectionNotFound');418 await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [999_999_999], true)).to.be.rejectedWith('common.CollectionNotFound');419 });420});421422describe('app-promotion contract sponsoring', () => {423 before(async function () {424 await beforeEach(this);425 });426427 itEth('should set palletes address as a sponsor', async ({helper}) => {428 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();429 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);430 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);431432 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);433 239 434 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 240 itSub('can be any valid CrossAccountId', async ({helper}) => {241 // We are not going to set an eth address as a sponsor,242 // but we do want to check, it doesn't break anything;243 const account = accounts.pop()!;244 const ethAccount = helper.address.substrateToEth(account.address); 245 // Alice sets Ethereum address as a sudo. Then Substrate address back...435 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 246 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled;436 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({247 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled;437 confirmed: {248 438 substrate: palletAddress,249 // ...It doesn't break anything;250 const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});439 },251 await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;440 });252 });253 254 itSub('can be reassigned', async ({helper}) => {255 const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];256 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});257 258 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.fulfilled;259 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.fulfilled;260 await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;261 262 await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;263 });441 });264 });442265 443 itEth('should overwrite sponsoring mode and existed sponsor', async ({helper}) => {266 describe('collection sponsoring', () => {444 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();267 before(async function () {445 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);268 await usingPlaygrounds(async (helper) => {446 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);447448 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;269 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}));449450 // Contract is self sponsored451 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({452 confirmed: {453 ethereum: flipper.options.address.toLowerCase(),270 await helper.signTransaction(alice, tx);454 },271 });455 });272 });456273 457 // set promotion sponsoring274 itSub('should actually sponsor transactions', async ({helper}) => {458 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);275 const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!];459460 // new sponsor is pallet address276 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}});461 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 277 const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address});462 expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner); 278 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));279 const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);280 463 expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({281 await token.transfer(tokenSender, {Substrate: receiver.address});282 expect (await token.getOwner()).to.be.deep.equal({Substrate: receiver.address});464 confirmed: {283 const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);465 substrate: palletAddress,284 285 // senders balance the same, transaction has sponsored466 },286 expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);287 expect (palletBalanceBefore > palletBalanceAfter).to.be.true;467 });288 });468 });289 469470 itEth('can be overwritten by contract owner', async ({helper}) => {290 itSub('can not be set by non admin', async ({helper}) => {471 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();291 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];472 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);473 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);474475 // contract sponsored by pallet292 476 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);293 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});477478 // owner sets self sponsoring294 479 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;295 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;480481 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 482 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 296 expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled');483 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({484 confirmed: {485 ethereum: flipper.options.address.toLowerCase(),486 },487 });297 });488 });298 489490 itEth('can not be set by non admin', async ({helper}) => {299 itSub('should set pallet address as confirmed admin', async ({helper}) => {491 const nonAdmin = accounts.pop()!;300 const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!];492 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();301 302 // Can set sponsoring for collection without sponsor493 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);303 const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'});494 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);304 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled;495496 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;305 expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});497306 498 // nonAdmin calls sponsorContract307 // Can set sponsoring for collection with unconfirmed sponsor499 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');308 const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});500309 expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address});501 // contract still self-sponsored 310 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled;502 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({311 expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});503 confirmed: {312 313 // Can set sponsoring for collection with confirmed sponsor504 ethereum: flipper.options.address.toLowerCase(),314 const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address});315 await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor);316 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled;505 },317 expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});506 });318 });507 });319 508509 itEth('should actually sponsor transactions', async ({helper}) => {320 itSub('can be overwritten by collection owner', async ({helper}) => { 510 // Contract caller321 const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!];511 const caller = await helper.eth.createAccountWithBalance(alice, 1000n);322 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});512 const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);323 const collectionId = collection.collectionId;513 324 514 // Deploy flipper325 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled;326 327 // Collection limits still can be changed by the owner515 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();328 expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true;329 expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);516 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);330 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});331 332 // Collection sponsor can be changed too333 expect((await collection.setSponsor(collectionOwner, newSponsor.address))).to.be.true;517 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);334 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: newSponsor.address});335 });518 336 519 // Owner sets to sponsor every tx337 itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => {520 await contractHelper.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: contractOwner});521 await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});338 const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0};522 await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address, 1000n); // transferBalanceToEth(api, alice, flipper.options.address, 1000n);339 const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits});523524 // Set promotion to the Flipper340 525 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorContract(flipper.options.address));341 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled;526527 // Caller calls Flipper528 await flipper.methods.flip().send({from: caller});529 expect(await flipper.methods.getValue().call()).to.be.true;342 expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits);530531 // The contracts and caller balances have not changed343 });344 532 const callerBalance = await helper.balance.getEthereum(caller);345 itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => {533 const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);346 const collectionOwner = accounts.pop()!;534 expect(callerBalance).to.be.equal(1000n * nominal);347 535 expect(1000n * nominal === contractBalanceAfter).to.be.true;348 // collection has never existed536349 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;537 // The pallet balance has decreased350 // collection has been burned538 const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);351 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});539 expect(palletBalanceAfter < palletBalanceBefore).to.be.true;352 await collection.burn(collectionOwner);353 354 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected;355 });540 });356 });541});357 542543describe('app-promotion stopSponsoringContract', () => { 358 describe('stopSponsoringCollection', () => {544 before(async function () {359 itSub('can not be called by non-admin', async ({helper}) => { 545 await beforeEach(this);546 });547548 itEth('should remove pallet address from contract sponsors', async ({helper}) => {549 const caller = await helper.eth.createAccountWithBalance(alice, 1000n);360 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];550 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();551 const flipper = await helper.eth.deployFlipper(contractOwner);361 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});552 await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address);553 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);554555 await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});556 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);362 557 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true);363 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled;558559 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false; 364 560 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 365 await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;561 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({366 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress});562 disabled: null,563 });367 });564368 565 await flipper.methods.flip().send({from: caller});369 itSub('should set sponsoring as disabled', async ({helper}) => {370 const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!];371 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}});372 const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address});566 expect(await flipper.methods.getValue().call()).to.be.true;373 567374 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId));375 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId));376 377 expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');378 568 const callerBalance = await helper.balance.getEthereum(caller);379 // Transactions are not sponsored anymore:380 const ownerBalanceBefore = await helper.balance.getSubstrate(collectionOwner.address);569 const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);381 await token.transfer(collectionOwner, {Substrate: recepient.address});570382 const ownerBalanceAfter = await helper.balance.getSubstrate(collectionOwner.address);383 expect(ownerBalanceAfter < ownerBalanceBefore).to.be.equal(true);384 });385 386 itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => {387 const collectionOwner = accounts.pop()!;388 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address});389 await collection.confirmSponsorship(collectionOwner);571 // caller payed for call390 572 expect(1000n * nominal > callerBalance).to.be.true;391 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected;392 393 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address});394 });395 396 itSub('should reject transaction if collection does not exist', async ({helper}) => { 397 const collectionOwner = accounts.pop()!;398 const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});399 400 await collection.burn(collectionOwner);401 await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [collection.collectionId], true)).to.be.rejectedWith('common.CollectionNotFound');573 expect(contractBalanceAfter).to.be.equal(1000n * nominal);402 await expect(helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringCollection', [999_999_999], true)).to.be.rejectedWith('common.CollectionNotFound');403 });574 });404 });575405 576 itEth('can not be called by non-admin', async ({helper}) => {406 describe('contract sponsoring', () => {577 const nonAdmin = accounts.pop()!;407 itEth('should set palletes address as a sponsor', async ({helper}) => {578 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();408 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();579 const flipper = await helper.eth.deployFlipper(contractOwner);409 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);580410 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);581 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);411 582 const stopSponsoringResult = await helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]);412 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);583 expect(stopSponsoringResult.status).to.equal('Fail');584 expect(stopSponsoringResult.moduleError).to.equal('appPromotion.NoPermission');585 });586587 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {588 const nonAdmin = accounts.pop()!;589 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();590 const flipper = await helper.eth.deployFlipper(contractOwner);591 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);592 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;593594 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');595 });596});597598describe('app-promotion rewards', () => {599 before(async function () {600 await beforeEach(this);601 });602603 itSub('can not be called by non admin', async ({helper}) => {604 const nonAdmin = accounts.pop()!;605 await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');606 });607608 itSub('should increase total staked', async ({helper}) => {609 const staker = accounts.pop()!;610 const totalStakedBefore = await helper.staking.getTotalStaked();611 await helper.staking.stake(staker, 100n * nominal);612613 // Wait for rewards and pay614 const [stakedInBlock] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});615 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock.block));616 const totalPayout = (await helper.admin.payoutStakers(palletAdmin, 100)).reduce((prev, payout) => prev + payout.payout, 0n);617618 const totalStakedAfter = await helper.staking.getTotalStaked();619 expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);620 // staker can unstake621 await helper.staking.unstake(staker);622 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal, 10n));623 });624625 itSub('should credit 0.05% for staking period', async ({helper}) => { 626 const staker = accounts.pop()!;627628 await waitPromotionPeriodDoesntEnd(helper);629 413 630 await helper.staking.stake(staker, 100n * nominal);414 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 631 await helper.staking.stake(staker, 200n * nominal);632633 // wait rewards are available:634 const [_, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});635 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));636637 const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)?.payout;415 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 638 expect(payoutToStaker + 300n * nominal).to.equal(calculateIncome(300n * nominal, 10n));639640 const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});641 expect(totalStakedPerBlock[0].amount).to.equal(calculateIncome(100n * nominal, 10n));642 expect(totalStakedPerBlock[1].amount).to.equal(calculateIncome(200n * nominal, 10n));643 });644645 itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {416 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({646 const staker = accounts.pop()!;647648 await helper.staking.stake(staker, 100n * nominal);649 // wait for two rewards are available:650 let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});651 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);652653 await helper.admin.payoutStakers(palletAdmin, 100);654 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});417 confirmed: {655 const frozenBalanceShouldBe = calculateIncome(100n * nominal, 10n, 2);418 substrate: palletAddress,656 expect(stake.amount).to.be.equal(frozenBalanceShouldBe);419 },657658 const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);659660 expect(stakerFullBalance).to.contain({reserved: 0n, feeFrozen: frozenBalanceShouldBe, miscFrozen: frozenBalanceShouldBe});420 });661 });421 });662 422 663 itSub('should not be credited for unstaked (reserved) balance', async ({helper}) => {423 itEth('should overwrite sponsoring mode and existed sponsor', async ({helper}) => {664 // staker unstakes before rewards has been payed424 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();665 const staker = accounts.pop()!;425 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);666 await helper.staking.stake(staker, 100n * nominal);426 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);667 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});427 668 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);428 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;669 await helper.staking.unstake(staker);429 430 // Contract is self sponsored431 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({432 confirmed: {433 ethereum: flipper.options.address.toLowerCase(),434 },435 });436 437 // set promotion sponsoring438 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);439 440 // new sponsor is pallet address441 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 442 expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner); 443 expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({444 confirmed: {445 substrate: palletAddress,446 },447 });448 });449 450 itEth('can be overwritten by contract owner', async ({helper}) => {451 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();452 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);453 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);454 455 // contract sponsored by pallet456 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);457 458 // owner sets self sponsoring459 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;460 461 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; 462 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 463 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({464 confirmed: {465 ethereum: flipper.options.address.toLowerCase(),466 },467 });468 });469 470 itEth('can not be set by non admin', async ({helper}) => {471 const nonAdmin = accounts.pop()!;472 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();473 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);474 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);475 476 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;477 478 // nonAdmin calls sponsorContract479 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');480 481 // contract still self-sponsored 482 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({483 confirmed: {484 ethereum: flipper.options.address.toLowerCase(),485 },486 });487 });488 489 itEth('should actually sponsor transactions', async ({helper}) => {490 // Contract caller491 const caller = await helper.eth.createAccountWithBalance(alice, 1000n);492 const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);493 494 // Deploy flipper495 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();496 const flipper = await helper.eth.deployFlipper(contractOwner); // await deployFlipper(web3, contractOwner);497 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);670 498 671 // so he did not receive any rewards499 // Owner sets to sponsor every tx500 await contractHelper.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: contractOwner});672 const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);501 await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});502 await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address, 1000n); // transferBalanceToEth(api, alice, flipper.options.address, 1000n);673 await helper.admin.payoutStakers(palletAdmin, 100);503 504 // Set promotion to the Flipper505 await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorContract(flipper.options.address));674 const totalBalanceAfter = await helper.balance.getSubstrate(staker.address);506 675507 // Caller calls Flipper508 await flipper.methods.flip().send({from: caller});509 expect(await flipper.methods.getValue().call()).to.be.true;510 511 // The contracts and caller balances have not changed512 const callerBalance = await helper.balance.getEthereum(caller);513 const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);676 expect(totalBalanceBefore).to.be.equal(totalBalanceAfter);514 expect(callerBalance).to.be.equal(1000n * nominal);515 expect(1000n * nominal === contractBalanceAfter).to.be.true;516 517 // The pallet balance has decreased518 const palletBalanceAfter = await helper.balance.getSubstrate(palletAddress);519 expect(palletBalanceAfter < palletBalanceBefore).to.be.true;520 });677 });521 });678 522 679 itSub('should bring compound interest', async ({helper}) => {523 describe('stopSponsoringContract', () => { 680 const staker = accounts.pop()!;524 itEth('should remove pallet address from contract sponsors', async ({helper}) => {681 525 const caller = await helper.eth.createAccountWithBalance(alice, 1000n);682 await helper.staking.stake(staker, 100n * nominal);526 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();683527 const flipper = await helper.eth.deployFlipper(contractOwner);684 let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});528 await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address);685 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));529 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);686 530 687 await helper.admin.payoutStakers(palletAdmin, 100);531 await contractHelper.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: contractOwner});688 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});532 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);689 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n));533 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true);690 534 691 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);535 expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false; 692 await helper.admin.payoutStakers(palletAdmin, 100);536 expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); 693 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});537 expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({694 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n, 2));538 disabled: null,539 });540 541 await flipper.methods.flip().send({from: caller});542 expect(await flipper.methods.getValue().call()).to.be.true;543 544 const callerBalance = await helper.balance.getEthereum(caller);545 const contractBalanceAfter = await helper.balance.getEthereum(flipper.options.address);546 547 // caller payed for call548 expect(1000n * nominal > callerBalance).to.be.true;549 expect(contractBalanceAfter).to.be.equal(1000n * nominal);550 });551 552 itEth('can not be called by non-admin', async ({helper}) => {553 const nonAdmin = accounts.pop()!;554 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();555 const flipper = await helper.eth.deployFlipper(contractOwner);556 557 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]);558 const stopSponsoringResult = await helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address]);559 expect(stopSponsoringResult.status).to.equal('Fail');560 expect(stopSponsoringResult.moduleError).to.equal('appPromotion.NoPermission');561 });562 563 itEth('should not affect a contract which is not sponsored by pallete', async ({helper}) => {564 const nonAdmin = accounts.pop()!;565 const contractOwner = (await helper.eth.createAccountWithBalance(alice, 1000n)).toLowerCase();566 const flipper = await helper.eth.deployFlipper(contractOwner);567 const contractHelper = helper.ethNativeContract.contractHelpers(contractOwner);568 await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled;569 570 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');571 });695 });572 });696573 697 itSub.skip('can be paid 1000 rewards in a time', async ({helper}) => {574 describe('rewards', () => {698 // all other stakes should be unstaked575 itSub('can not be called by non admin', async ({helper}) => {699 const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);576 const nonAdmin = accounts.pop()!;700577 await expect(helper.admin.payoutStakers(nonAdmin, 100)).to.be.rejectedWith('appPromotion.NoPermission');701 // stakers stakes 10 times each578 });702 for (let i = 0; i < 10; i++) {579 703 await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));580 itSub('should increase total staked', async ({helper}) => {704 }581 const staker = accounts.pop()!;705 await helper.wait.newBlocks(40);582 const totalStakedBefore = await helper.staking.getTotalStaked();706 await helper.admin.payoutStakers(palletAdmin, 100);583 await helper.staking.stake(staker, 100n * nominal);707 });584 708585 // Wait for rewards and pay709 itSub.skip('can handle 40.000 rewards', async ({helper}) => {586 const [stakedInBlock] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});710 const [donor] = await helper.arrange.createAccounts([7_000_000n], alice);587 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stakedInBlock.block));711 const crowdStakes = async () => {588 const totalPayout = (await helper.admin.payoutStakers(palletAdmin, 100)).reduce((prev, payout) => prev + payout.payout, 0n);712 // each account in the crowd stakes 2 times589 713 const crowd = await helper.arrange.createCrowd(500, 300n, donor);590 const totalStakedAfter = await helper.staking.getTotalStaked();714 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));591 expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);715 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));592 // staker can unstake716 // 593 await helper.staking.unstake(staker);717 };594 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal, 10n));718595 });719 for (let i = 0; i < 40; i++) {596 720 await crowdStakes();597 itSub('should credit 0.05% for staking period', async ({helper}) => { 721 }598 const staker = accounts.pop()!;722599 723 // TODO pay rewards for some period600 await waitPromotionPeriodDoesntEnd(helper);601 602 await helper.staking.stake(staker, 100n * nominal);603 await helper.staking.stake(staker, 200n * nominal);604 605 // wait rewards are available:606 const [_, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});607 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));608 609 const payoutToStaker = (await helper.admin.payoutStakers(palletAdmin, 100)).find((payout) => payout.staker === staker.address)?.payout;610 expect(payoutToStaker + 300n * nominal).to.equal(calculateIncome(300n * nominal, 10n));611 612 const totalStakedPerBlock = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});613 expect(totalStakedPerBlock[0].amount).to.equal(calculateIncome(100n * nominal, 10n));614 expect(totalStakedPerBlock[1].amount).to.equal(calculateIncome(200n * nominal, 10n));615 });616 617 itSub('shoud be paid for more than one period if payments was missed', async ({helper}) => {618 const staker = accounts.pop()!;619 620 await helper.staking.stake(staker, 100n * nominal);621 // wait for two rewards are available:622 let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});623 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);624 625 await helper.admin.payoutStakers(palletAdmin, 100);626 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});627 const frozenBalanceShouldBe = calculateIncome(100n * nominal, 10n, 2);628 expect(stake.amount).to.be.equal(frozenBalanceShouldBe);629 630 const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);631 632 expect(stakerFullBalance).to.contain({reserved: 0n, feeFrozen: frozenBalanceShouldBe, miscFrozen: frozenBalanceShouldBe});633 });634 635 itSub('should not be credited for unstaked (reserved) balance', async ({helper}) => {636 // staker unstakes before rewards has been payed637 const staker = accounts.pop()!;638 await helper.staking.stake(staker, 100n * nominal);639 const [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});640 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);641 await helper.staking.unstake(staker);642 643 // so he did not receive any rewards644 const totalBalanceBefore = await helper.balance.getSubstrate(staker.address);645 await helper.admin.payoutStakers(palletAdmin, 100);646 const totalBalanceAfter = await helper.balance.getSubstrate(staker.address);647 648 expect(totalBalanceBefore).to.be.equal(totalBalanceAfter);649 });650 651 itSub('should bring compound interest', async ({helper}) => {652 const staker = accounts.pop()!;653 654 await helper.staking.stake(staker, 100n * nominal);655 656 let [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});657 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block));658 659 await helper.admin.payoutStakers(palletAdmin, 100);660 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});661 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n));662 663 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake.block) + LOCKING_PERIOD);664 await helper.admin.payoutStakers(palletAdmin, 100);665 [stake] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});666 expect(stake.amount).to.equal(calculateIncome(100n * nominal, 10n, 2));667 });668 669 itSub.skip('can be paid 1000 rewards in a time', async ({helper}) => {670 // all other stakes should be unstaked671 const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);672 673 // stakers stakes 10 times each674 for (let i = 0; i < 10; i++) {675 await Promise.all(oneHundredStakers.map(staker => helper.staking.stake(staker, 100n * nominal)));676 }677 await helper.wait.newBlocks(40);678 await helper.admin.payoutStakers(palletAdmin, 100);679 });680 681 itSub.skip('can handle 40.000 rewards', async ({helper}) => {682 const [donor] = await helper.arrange.createAccounts([7_000_000n], alice);683 const crowdStakes = async () => {684 // each account in the crowd stakes 2 times685 const crowd = await helper.arrange.createCrowd(500, 300n, donor);686 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));687 await Promise.all(crowd.map(account => helper.staking.stake(account, 100n * nominal)));688 // 689 };690 691 for (let i = 0; i < 40; i++) {692 await crowdStakes();693 }694 695 // TODO pay rewards for some period696 });724 });697 });725});698});726699tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth122 */122 */123 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {123 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {124 let nonce = await this.helper.chain.getNonce(donor.address);124 let nonce = await this.helper.chain.getNonce(donor.address);125 const wait = new WaitGroup(this.helper);125 const ss58Format = this.helper.chain.getChainProperties().ss58Format;126 const ss58Format = this.helper.chain.getChainProperties().ss58Format;126 const tokenNominal = this.helper.balance.getOneTokenNominal();127 const tokenNominal = this.helper.balance.getOneTokenNominal();127 const transactions = [];128 const transactions = [];156 for (let index = 0; index < 5; index++) {157 for (let index = 0; index < 5; index++) {157 accountsCreated = await checkBalances();158 accountsCreated = await checkBalances();158 if(accountsCreated) break;159 if(accountsCreated) break;159 160 await wait.newBlocks(1);160 }161 }161162162 if (!accountsCreated) throw Error('Accounts generation failed');163 if (!accountsCreated) throw Error('Accounts generation failed');