git.delta.rocks / unique-network / refs/commits / 5fb293eff7dc

difftreelog

Fix test: fix create accounts script for devnode

Max Andreev2022-09-15parent: #343d804.patch.diff
in: master
- add waiter for create accounts script
- wrap promotion tests in global describe to avoid accounts creation
for each describe

2 files changed

modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain36const UNLOCKING_PERIOD = 10n; // 10 blocks of parachain
37const rewardAvailableInBlock = (stakedInBlock: bigint) => (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);37const rewardAvailableInBlock = (stakedInBlock: bigint) => (stakedInBlock - stakedInBlock % LOCKING_PERIOD) + (LOCKING_PERIOD * 2n);
3838
39const 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 address
44 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 tests
49 });
50};
51
52describe('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 address
45 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 tests
50 });
55 });51 });
5652
57 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 900
68 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 stakes
87 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 again
93 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 fee
102 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 reserved
129 // 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);
88
89 // can have 10 stakes
90 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);
92
93 await expect(helper.staking.stake(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.NoPermission');
94
95 // After unstake can stake again
96 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: 0n
102 const staker = accounts.pop()!;144 await helper.wait.forParachainBlockNumber(pendingUnstake.block);
103
104 // 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);
107
108 // 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: 0
125 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 stakes
135 // 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 all
153 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 helper
156 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 pendingUnstakes
160 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);
165
166 // staked: [100, 200, 300]; unstaked: 0
167 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 stakes
177 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);
184
185 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 });
190
191 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 all
195 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 helper
198
199 // TODO stake() unstake() waitUnstaked() unstake();
200
201 // can't unstake if there are only pendingUnstakes
202 await helper.staking.stake(staker, 100n * nominal);
203 await helper.staking.unstake(staker);
204 await helper.staking.unstake(staker);
205
206 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 });
209
210 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);
216
217 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 });
222
223 itSub('should be possible for different accounts in one block', async ({helper}) => {
224 const stakers = [accounts.pop()!, accounts.pop()!, accounts.pop()!];
225
226 await Promise.all(stakers.map(staker => helper.staking.stake(staker, 100n * nominal)));
227 await Promise.all(stakers.map(staker => helper.staking.unstake(staker)));
228
229 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});
235
236describe('Admin adress', () => {
237 before(async function () {
238 await beforeEach(this);
239 });
240
241 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 sudo
244 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;
246
247 // Alice can
248 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
264
265 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});
276
277describe('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 });
285
286 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);
292
293 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);
296
297 // senders balance the same, transaction has sponsored
298 expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);
299 expect (palletBalanceBefore > palletBalanceAfter).to.be.true;
300 });
301
302 itSub('can not be set by non admin', async ({helper}) => {
303 const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!];
304
305 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 });
310
311 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 sponsor
315 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});
318
319 // Can set sponsoring for collection with unconfirmed sponsor
320 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});
324
325 // Can set sponsoring for collection with confirmed sponsor
326 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 });
331
332 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 owner
340 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});
343
344 // Collection sponsor can be changed too
345 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});
352
353 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 burned
363 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 }));
365
366 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 sudo
373 });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 can
377 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 });
384
385 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');
394
395 // 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 });
401
402 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 });
411
412 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});
421
422describe('app-promotion contract sponsoring', () => {
423 before(async function () {
424 await beforeEach(this);
425 });
426
427 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);
431
432 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);
447
448 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}));
449
450 // Contract is self sponsored
451 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()!];
459
460 // 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 sponsored
466 },286 expect (await helper.balance.getSubstrate(tokenSender.address)).to.be.equal(1000n * nominal);
287 expect (palletBalanceBefore > palletBalanceAfter).to.be.true;
467 });288 });
468 });289
469
470 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);
474
475 // 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'});
477
478 // 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;
480
481 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
489
490 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 sponsor
493 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;
495
496 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 sponsor
499 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 sponsor
504 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
508
509 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 owner
515 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 too
333 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});
523
524 // 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;
526
527 // Caller calls Flipper
528 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);
530
531 // 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 existed
536349 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected;
537 // The pallet balance has decreased350 // collection has been burned
538 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
542
543describe('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 });
547
548 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);
554
555 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;
558
559 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 });
586
587 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;
593
594 await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission');
595 });
596});
597
598describe('app-promotion rewards', () => {
599 before(async function () {
600 await beforeEach(this);
601 });
602
603 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 });
607
608 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);
612
613 // Wait for rewards and pay
614 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);
617
618 const totalStakedAfter = await helper.staking.getTotalStaked();
619 expect(totalStakedAfter).to.equal(totalStakedBefore + (100n * nominal) + totalPayout);
620 // staker can unstake
621 await helper.staking.unstake(staker);
622 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedAfter - calculateIncome(100n * nominal, 10n));
623 });
624
625 itSub('should credit 0.05% for staking period', async ({helper}) => {
626 const staker = accounts.pop()!;
627
628 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);
632
633 // wait rewards are available:
634 const [_, stake2] = await helper.staking.getTotalStakedPerBlock({Substrate: staker.address});
635 await helper.wait.forRelayBlockNumber(rewardAvailableInBlock(stake2.block));
636
637 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));
639
640 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 });
644
645 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()!;
647
648 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);
652
653 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 },
657
658 const stakerFullBalance = await helper.balance.getSubstrateFull(staker.address);
659
660 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 sponsored
431 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 sponsoring
438 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);
439
440 // new sponsor is pallet address
441 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 pallet
456 await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true);
457
458 // owner sets self sponsoring
459 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 sponsorContract
479 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 caller
491 const caller = await helper.eth.createAccountWithBalance(alice, 1000n);
492 const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress);
493
494 // Deploy flipper
495 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 tx
500 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 Flipper
505 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 Flipper
508 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 changed
512 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 decreased
518 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 call
548 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 pay
709 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 unstake
716 // 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 payed
637 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 rewards
644 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 unstaked
671 const oneHundredStakers = await helper.arrange.createCrowd(100, 1050n, alice);
672
673 // stakers stakes 10 times each
674 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 times
685 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 period
696 });
724 });697 });
725});698});
726699
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
122 */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 }
161162
162 if (!accountsCreated) throw Error('Accounts generation failed');163 if (!accountsCreated) throw Error('Accounts generation failed');