git.delta.rocks / unique-network / refs/commits / 0095393c2f89

difftreelog

Add helpers and refactor some tests

Max Andreev2022-08-27parent: #ac1baa6.patch.diff
in: master

2 files changed

modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
2928
30import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';29import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
31import {stringToU8a} from '@polkadot/util';30import {stringToU8a} from '@polkadot/util';
32import {UniqueHelper} from './util/playgrounds/unique';
33import {ApiPromise} from '@polkadot/api';31import {ApiPromise} from '@polkadot/api';
34chai.use(chaiAsPromised);32chai.use(chaiAsPromised);
35const expect = chai.expect;33const expect = chai.expect;
40let nominal: bigint;38let nominal: bigint;
41let promotionStartBlock: number | null = null;39let promotionStartBlock: number | null = null;
40
41before(async function () {
42 await usingPlaygrounds(async (helper, privateKeyWrapper) => {
43 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();
44 alice = privateKeyWrapper('//Alice');
45 bob = privateKeyWrapper('//Bob');
46 palletAdmin = privateKeyWrapper('//palletAdmin');
47 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));
48 await helper.signTransaction(alice, tx);
49 nominal = helper.balance.getOneTokenNominal();
50 });
51});
4252
43describe('app-promotions.stake extrinsic', () => {53describe('app-promotions.stake extrinsic', () => {
44 before(async function() {
45 await usingPlaygrounds(async (helper, privateKeyWrapper) => {
46 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();
47 alice = privateKeyWrapper('//Alice');
48 bob = privateKeyWrapper('//Bob');
49 palletAdmin = privateKeyWrapper('//palletAdmin');
50 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));
51 nominal = helper.balance.getOneTokenNominal();
52 await helper.signTransaction(alice, tx);
53 });
54 });
55 it('will change balance state to "locked", add it to "staked" map, and increase "totalStaked" amount', async () => {54 it('will change balance state to "locked", add it to "staked" map, and increase "totalStaked" amount', async () => {
56 // arrange: Alice balance = 1000
57 // act: Alice calls appPromotion.stake(100)
58 // assert: Alice locked balance equal 100
59 // assert: Alice free balance closeTo 900
60 // assert: query appPromotion.staked(Alice) equal [100]
61 // assert: query appPromotion.totalStaked() increased by 100
62 // act: Alice extrinsic appPromotion.stake(200)
63
64 // assert: Alice locked balance equal 300
65 // assert: query appPromotion.staked(Alice) equal [100, 200]
66 // assert: query appPromotion.totalStaked() increased by 200
67
68 await usingPlaygrounds(async (helper) => {55 await usingPlaygrounds(async (helper) => {
69 const totalStakedBefore = await helper.staking.getTotalStaked();56 const totalStakedBefore = await helper.staking.getTotalStaked();
70 const staker = await createUser();57 const [staker] = await helper.arrange.creteAccounts([10n], alice);
71 58
72 await expect(helper.staking.stake(staker, nominal - 1n)).to.be.eventually.rejected;59 await expect(helper.staking.stake(staker, nominal - 1n)).to.be.eventually.rejected; // minimum stake amount is 1
73 await expect(helper.staking.stake(staker, nominal * 1n)).to.be.eventually.fulfilled;60 await expect(helper.staking.stake(staker, nominal)).to.be.eventually.fulfilled;
74 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(nominal);61 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(nominal);
7562
76 // TODO add helpers to assert bigints. Check balance close to 1063 // TODO add helpers to assert bigints. Check balance close to 10
77 expect(await helper.balance.getSubstrate(staker.address) - 9n * nominal >= (nominal / 2n)).to.be.true;64 expect(await helper.balance.getSubstrate(staker.address) - 9n * nominal >= (nominal / 2n)).to.be.true;
78 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(nominal);65 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(nominal);
66 // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo?
79 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + nominal);67 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + nominal); // total tokens amount staked in app-promotion increased
8068
81 await helper.arrange.waitNewBlocks(1);
82
83 await expect(helper.staking.stake(staker, 2n * nominal)).to.be.eventually.fulfilled;69 await expect(helper.staking.stake(staker, 2n * nominal)).to.be.eventually.fulfilled;
84 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(3n * nominal);70 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(3n * nominal);
89 });75 });
90 76
91 it('will throws if stake amount is more than total free balance', async () => {77 it('will throws if stake amount is more than total free balance', async () => {
92 // arrange: Alice balance = 1000
93 // assert: Alice calls appPromotion.stake(1000) throws /// because Alice needs some fee
94
95 // act: Alice calls appPromotion.stake(700)
96 // assert: Alice calls appPromotion.stake(400) throws /// because Alice has ~300 free QTZ and 700 locked
97
98 await usingPlaygrounds(async helper => { 78 await usingPlaygrounds(async helper => {
99
100 const staker = await createUser();79 const [staker] = await helper.arrange.creteAccounts([10n], alice);
101 80
102 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.rejected;81 await expect(helper.staking.stake(staker, 10n * nominal)).to.be.eventually.rejected; // because Alice needs some fee
103 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(7n * nominal))).to.be.eventually.fulfilled;82 await expect(helper.staking.stake(staker, 7n * nominal)).to.be.eventually.fulfilled;
104 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(4n * nominal))).to.be.eventually.rejected;83 await expect(helper.staking.stake(staker, 4n * nominal)).to.be.eventually.rejected; // because Alice has ~300 free QTZ and 700 locked
105 84 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(7n * nominal);
106 });85 });
107 });86 });
108 87
109 it('for different accounts in one block is possible', async () => {88 it('for different accounts in one block is possible', async () => {
110 // arrange: Alice, Bob, Charlie, Dave balance = 1000
111 // arrange: Alice, Bob, Charlie, Dave calls appPromotion.stake(100) in the same time
112
113 // assert: query appPromotion.staked(Alice/Bob/Charlie/Dave) equal [100]
114 await usingPlaygrounds(async helper => {89 await usingPlaygrounds(async helper => {
115 const crowd = [];90 const crowd = await helper.arrange.creteAccounts([10n, 10n, 10n, 10n], alice);
116 for(let i = 4; i--;) crowd.push(await createUser());91
117
118 const promises = crowd.map(async user => helper.signTransaction(user, helper.api!.tx.promotion.stake(nominal)));92 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, nominal));
119 await expect(Promise.all(promises)).to.be.eventually.fulfilled;93 await expect(Promise.all(crowdStartsToStake)).to.be.eventually.fulfilled;
120 94
121 for (let i = 0; i < crowd.length; i++){95 const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));
122 expect((await helper.api!.rpc.unique.totalStaked(normalizeAccountId(crowd[i]))).toBigInt()).to.be.equal(nominal); 96 expect(crowdStakes).to.deep.equal([nominal, nominal, nominal, nominal]);
123 }
124 });97 });
125 });98 });
12699 // TODO it('Staker stakes 5 times in one block with nonce');
100 // TODO it('Staked balance appears as locked in the balance pallet')
127});101});
128102
129describe('unstake balance extrinsic', () => {103describe('unstake balance extrinsic', () => {
130 before(async function () {
131 await usingPlaygrounds(async (helper, privateKeyWrapper) => {
132 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();
133 alice = privateKeyWrapper('//Alice');
134 bob = privateKeyWrapper('//Bob');
135 palletAdmin = privateKeyWrapper('//palletAdmin');
136 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));
137 nominal = helper.balance.getOneTokenNominal();
138 await helper.signTransaction(alice, tx);
139 });
140 });
141
142 it('will change balance state to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async () => {104 it('will change balance state to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async () => {
143 // arrange: Alice balance = 1000
144 // arrange: Alice calls appPromotion.stake(Alice, 500)
145
146 // act: Alice calls appPromotion.unstake(300)
147 // assert: Alice reserved balance to equal 300
148 // assert: query appPromotion.staked(Alice) equal [200] /// 500 - 300
149 // assert: query appPromotion.pendingUnstake(Alice) to equal [300]
150 // assert: query appPromotion.totalStaked() decreased by 300
151 await usingPlaygrounds(async helper => {105 await usingPlaygrounds(async helper => {
152 const totalStakedBefore = (await helper.api!.rpc.unique.totalStaked()).toBigInt();106 const totalStakedBefore = await helper.staking.getTotalStaked();
153 const staker = await createUser();107 const [staker] = await helper.arrange.creteAccounts([10n], alice);
154 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(5n * nominal))).to.be.eventually.fulfilled;108 await expect(helper.staking.stake(staker, 5n * nominal)).to.be.eventually.fulfilled;
155 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(3n * nominal))).to.be.eventually.fulfilled;109 await expect(helper.staking.unstake(staker, 3n * nominal)).to.be.eventually.fulfilled;
110
156 expect((await helper.api!.rpc.unique.pendingUnstake(normalizeAccountId(staker.address))).toBigInt()).to.be.equal(3n * nominal);111 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(3n * nominal);
157 expect((await helper.api!.rpc.unique.totalStaked(normalizeAccountId(staker))).toBigInt()).to.be.equal(2n * nominal);112 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(2n * nominal);
158 expect((await helper.api!.rpc.unique.totalStaked()).toBigInt()).to.be.equal(totalStakedBefore + 2n * nominal);113 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 2n * nominal);
159 });114 });
160 });115 });
161 it('will remove from the "staked" map starting from the oldest entry', async () => {116 it('will remove from the "staked" map starting from the oldest entry', async () => {
838 });793 });
839}794}
840795
841const creteAccounts = async (balances: bigint[], donor: IKeyringPair, helper: UniqueHelper) => {
842 let nonce = await helper.chain.getNonce(donor.address);
843 const tokenNominal = helper.balance.getOneTokenNominal();
844 const transactions = [];
845 const accounts = [];
846 for (const balance of balances) {
847 const recepient = helper.util.fromSeed(mnemonicGenerate());
848 accounts.push(recepient);
849 if (balance !== 0n){
850 const tx = helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, balance * tokenNominal]);
851 transactions.push(helper.signTransaction(donor, tx, 'account generation', {nonce}));
852 nonce++;
853 }
854 }
855
856 await Promise.all(transactions);
857 return accounts;
858};
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
1980}1980}
19811981
1982class StakingGroup extends HelperGroup {1982class StakingGroup extends HelperGroup {
1983 /**1983 /**
1984 * Stake tokens for App Promotion1984 * Stake tokens for App Promotion
1985 * @param signer keyring of signer1985 * @param signer keyring of signer
1986 * @param amount amount of tokens to stake1986 * @param amountToStake amount of tokens to stake
1987 * @param label extra label for log1987 * @param label extra label for log
1988 * @returns
1988 */1989 */
1989 async stake(signer: TSigner, amount: bigint, label?: string): Promise<boolean> {1990 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {
1990 if(typeof label === 'undefined') label = `${signer.address} amount: ${amount}`;1991 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;
1991 const stakeResult = await this.helper.executeExtrinsic(1992 const stakeResult = await this.helper.executeExtrinsic(
1992 signer,1993 signer,
1993 'api.tx.promotion.stake', [amount],1994 'api.tx.promotion.stake', [amountToStake],
1994 true, `stake failed for ${label}`,1995 true, `stake failed for ${label}`,
1995 );1996 );
1996 // TODO extract info from events1997 // TODO extract info from stakeResult
1997 return true;1998 return true;
1998 }1999 }
2000
2001 /**
2002 * Unstake tokens for App Promotion
2003 * @param signer keyring of signer
2004 * @param amountToUnstake amount of tokens to unstake
2005 * @param label extra label for log
2006 * @returns
2007 */
2008 async unstake(signer: TSigner, amountToUnstake: bigint, label?: string): Promise<boolean> {
2009 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToUnstake}`;
2010 const unstakeResult = await this.helper.executeExtrinsic(
2011 signer,
2012 'api.tx.promotion.unstake', [amountToUnstake],
2013 true, `unstake failed for ${label}`,
2014 );
2015 // TODO extract info from unstakeResult
2016 return true;
2017 }
19992018
2000 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2019 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {
2001 if (address) return (await this.helper.callRpc('api.rpc.unique.totalStaked', [address])).toBigInt();2020 if (address) return (await this.helper.callRpc('api.rpc.unique.totalStaked', [address])).toBigInt();
2010 return (await this.helper.callRpc('api.rpc.unique.totalStakedPerBlock', [address])).map(([block, amount]: any[]) => [block.toBigInt(), amount.toBigInt()]);2029 return (await this.helper.callRpc('api.rpc.unique.totalStakedPerBlock', [address])).map(([block, amount]: any[]) => [block.toBigInt(), amount.toBigInt()]);
2011 }2030 }
2031
2032 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {
2033 return (await this.helper.callRpc('api.rpc.unique.pendingUnstake')).toBigInt();
2034 }
2012}2035}
20132036
2014export class UniqueHelper extends ChainHelperBase {2037export class UniqueHelper extends ChainHelperBase {