difftreelog
Add missing tests
in: master
1 file changed
tests/src/app-promotion.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {19 normalizeAccountId,20 getModuleNames,21 Pallets,22} from './util/helpers';2324import chai from 'chai';25import chaiAsPromised from 'chai-as-promised';26import {usingPlaygrounds} from './util/playgrounds';2728import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';29import {stringToU8a} from '@polkadot/util';30import {ApiPromise} from '@polkadot/api';31chai.use(chaiAsPromised);32const expect = chai.expect;3334let alice: IKeyringPair;35let bob: IKeyringPair;36let palletAdmin: IKeyringPair;37let nominal: bigint;38let promotionStartBlock: number | null = null;3940before(async function () {41 await usingPlaygrounds(async (helper, privateKeyWrapper) => {42 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();43 alice = privateKeyWrapper('//Alice');44 bob = privateKeyWrapper('//Bob');45 palletAdmin = privateKeyWrapper('//palletAdmin');46 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));47 await helper.signTransaction(alice, tx);48 nominal = helper.balance.getOneTokenNominal();49 });50});5152describe('app-promotions.stake extrinsic', () => {53 it('will change balance state to "locked", add it to "staked" map, and increase "totalStaked" amount', async () => {54 await usingPlaygrounds(async (helper) => {55 const totalStakedBefore = await helper.staking.getTotalStaked();56 const [staker] = await helper.arrange.creteAccounts([10n], alice);57 58 // Minimum stake amount is 1:59 await expect(helper.staking.stake(staker, nominal - 1n)).to.be.eventually.rejected;60 await helper.staking.stake(staker, nominal);61 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(nominal);6263 // TODO add helpers to assert bigints. Check balance close to 1064 expect(await helper.balance.getSubstrate(staker.address) - 9n * nominal >= (nominal / 2n)).to.be.true;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? 67 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + nominal); // total tokens amount staked in app-promotion increased 6869 await helper.staking.stake(staker, 2n * nominal);70 expect(await helper.staking.getTotalStakingLocked({Substrate: staker.address})).to.be.equal(3n * nominal);71 72 const stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map((x) => x[1]);73 expect(stakedPerBlock).to.be.deep.equal([nominal, 2n * nominal]);74 });75 });76 77 it('will throws if stake amount is more than total free balance', async () => {78 await usingPlaygrounds(async helper => { 79 const [staker] = await helper.arrange.creteAccounts([10n], alice);8081 // Can't stake full balance because Alice needs to pay some fee82 await expect(helper.staking.stake(staker, 10n * nominal)).to.be.eventually.rejected;83 await helper.staking.stake(staker, 7n * nominal);8485 // Can't stake 4 tkn because Alice has ~3 free tkn, and 7 locked86 await expect(helper.staking.stake(staker, 4n * nominal)).to.be.eventually.rejected; 87 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(7n * nominal);88 });89 });90 91 it('for different accounts in one block is possible', async () => {92 await usingPlaygrounds(async helper => {93 const crowd = await helper.arrange.creteAccounts([10n, 10n, 10n, 10n], alice);94 95 const crowdStartsToStake = crowd.map(user => helper.staking.stake(user, nominal));96 await expect(Promise.all(crowdStartsToStake)).to.be.eventually.fulfilled;9798 const crowdStakes = await Promise.all(crowd.map(address => helper.staking.getTotalStaked({Substrate: address.address})));99 expect(crowdStakes).to.deep.equal([nominal, nominal, nominal, nominal]);100 });101 });102 // TODO it('Staker stakes 5 times in one block with nonce');103 // TODO it('Staked balance appears as locked in the balance pallet');104 // TODO it('Alice stakes huge amount of tokens');105 // TODO it('Can stake from ethereum account')106});107108describe('unstake balance extrinsic', () => { 109 it('will change balance state to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async () => {110 await usingPlaygrounds(async helper => {111 const totalStakedBefore = await helper.staking.getTotalStaked();112 const [staker] = await helper.arrange.creteAccounts([10n], alice);113 await helper.staking.stake(staker, 5n * nominal);114 await helper.staking.unstake(staker, 3n * nominal);115116 expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(3n * nominal);117 expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(2n * nominal);118 expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 2n * nominal);119 });120 });121122 it('will remove from the "staked" map starting from the oldest entry', async () => {123 await usingPlaygrounds(async helper => {124 const [staker] = await helper.arrange.creteAccounts([100n], alice);125 await helper.staking.stake(staker, 10n * nominal);126 await helper.staking.stake(staker, 20n * nominal);127 await helper.staking.stake(staker, 30n * nominal);128129 // staked: [10, 20, 30]; unstaked: 0130 let pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});131 let unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);132 let stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);133 expect(pendingUnstake).to.be.deep.equal(0n);134 expect(unstakedPerBlock).to.be.deep.equal([]);135 expect(stakedPerBlock).to.be.deep.equal([10n * nominal, 20n * nominal, 30n * nominal]);136 137 // Can unstake the part of a stake138 await helper.staking.unstake(staker, 5n * nominal);139 pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});140 unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);141 stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);142 expect(pendingUnstake).to.be.equal(5n * nominal);143 expect(stakedPerBlock).to.be.deep.equal([5n * nominal, 20n * nominal, 30n * nominal]);144 expect(unstakedPerBlock).to.be.deep.equal([5n * nominal]);145146 // Can unstake one stake totally and one more partially147 await helper.staking.unstake(staker, 10n * nominal);148 pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});149 unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);150 stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);151 expect(pendingUnstake).to.be.equal(15n * nominal);152 expect(stakedPerBlock).to.be.deep.equal([15n * nominal, 30n * nominal]);153 expect(unstakedPerBlock).to.deep.equal([5n * nominal, 10n * nominal]);154155 // Can totally unstake 2 stakes in one tx156 await helper.staking.unstake(staker, 45n * nominal);157 pendingUnstake = await helper.staking.getPendingUnstake({Substrate: staker.address});158 unstakedPerBlock = (await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address})).map(stake => stake[1]);159 stakedPerBlock = (await helper.staking.getTotalStakedPerBlock({Substrate: staker.address})).map(stake => stake[1]);160 expect(pendingUnstake).to.be.equal(60n * nominal);161 expect(stakedPerBlock).to.deep.equal([]);162 expect(unstakedPerBlock).to.deep.equal([5n * nominal, 10n * nominal, 45n * nominal]);163 });164165 // TODO it('try to hack unstaking with nonce')166 });167});168169describe('Admin adress', () => {170 before(async function () {171 await usingPlaygrounds(async (helper, privateKeyWrapper) => {172 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();173 alice = privateKeyWrapper('//Alice');174 bob = privateKeyWrapper('//Bob');175 palletAdmin = privateKeyWrapper('//palletAdmin');176 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());177 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));178 179 await helper.signTransaction(alice, tx);180 181 nominal = helper.balance.getOneTokenNominal();182 });183 });184185 it('can be set by sudo only', async () => {186 // assert: Sudo calls appPromotion.setAdminAddress(Alice) /// Sudo successfully sets Alice as admin187 // assert: Bob calls appPromotion.setAdminAddress(Bob) throws /// Random account can not set admin188 // assert: Alice calls appPromotion.setAdminAddress(Bob) throws /// Admin account can not set admin189 await usingPlaygrounds(async (helper) => {190 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(alice))))).to.be.eventually.fulfilled;191 await expect(helper.signTransaction(bob, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.rejected;192 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.fulfilled;193 });194 195 });196 197 it('can be any valid CrossAccountId', async () => {198 /// We are not going to set an eth address as a sponsor,199 /// but we do want to check, it doesn't break anything;200201 // arrange: Charlie creates Punks202 // arrange: Sudo calls appPromotion.setAdminAddress(0x0...) success203 // arrange: Sudo calls appPromotion.setAdminAddress(Alice) success204 205 // assert: Alice calls appPromotion.sponsorCollection(Punks.id) success206 207 await usingPlaygrounds(async (helper) => {208 209 const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};210 211 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(ethAcc)))).to.be.eventually.fulfilled;212 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin))))).to.be.eventually.fulfilled;213 214 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});215 216 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;217 });218 219 });220221 it('can be reassigned', async () => {222 // arrange: Charlie creates Punks223 // arrange: Sudo calls appPromotion.setAdminAddress(Alice)224 // act: Sudo calls appPromotion.setAdminAddress(Bob)225226 // assert: Alice calls appPromotion.sponsorCollection(Punks.id) throws /// Alice can not set collection sponsor227 // assert: Bob calls appPromotion.sponsorCollection(Punks.id) successful /// Bob can set collection sponsor228229 // act: Sudo calls appPromotion.setAdminAddress(null) successful /// Sudo can set null as a sponsor230 // assert: Bob calls appPromotion.stopSponsoringCollection(Punks.id) throws /// Bob is no longer an admin231 232 await usingPlaygrounds(async (helper) => {233 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});234 235 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(alice))))).to.be.eventually.fulfilled;236 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.fulfilled;237 await expect(helper.signTransaction(alice, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;238 239 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;240 });241 242 });243244});245246describe('App-promotion collection sponsoring', () => {247 before(async function () {248 await usingPlaygrounds(async (helper, privateKeyWrapper) => {249 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();250 alice = privateKeyWrapper('//Alice');251 bob = privateKeyWrapper('//Bob');252 palletAdmin = privateKeyWrapper('//palletAdmin');253 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());254 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());255 256 257 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));258 await helper.signTransaction(alice, tx);259 260 // const txStart = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.startAppPromotion(promotionStartBlock));261 // await helper.signTransaction(alice, txStart);262 263 nominal = helper.balance.getOneTokenNominal();264 });265 });266 267 it('can not be set by non admin', async () => {268 269 270 // arrange: Charlie creates Punks271 // arrange: Sudo calls appPromotion.setAdminAddress(Alice)272273 // assert: Random calls appPromotion.sponsorCollection(Punks.id) throws /// Random account can not set sponsoring274 // assert: Alice calls appPromotion.sponsorCollection(Punks.id) success /// Admin account can set sponsoring275 276 await usingPlaygrounds(async (helper) => {277 const colletcion = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});278 279 const collectionId = colletcion.collectionId;280 281 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.rejected;282 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;283 });284 285 });286287 it('will set pallet address as confirmed admin for collection without sponsor', async () => {288 // arrange: Charlie creates Punks289290 // act: Admin calls appPromotion.sponsorCollection(Punks.id)291292 // assert: query collectionById: Punks sponsoring is confirmed by PalleteAddress293 294 await usingPlaygrounds(async (helper) => {295 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});296 const collectionId = collection.collectionId;297298 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;299 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});300 });301 302 });303304 it('will set pallet address as confirmed admin for collection with unconfirmed sponsor', async () => {305 // arrange: Charlie creates Punks306 // arrange: Charlie calls setCollectionSponsor(Punks.Id, Dave) /// Dave is unconfirmed sponsor307308 // act: Admin calls appPromotion.sponsorCollection(Punks.id)309310 // assert: query collectionById: Punks sponsoring is confirmed by PalleteAddress311 312 await usingPlaygrounds(async (helper) => {313 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});314 await collection.setSponsor(alice, bob.address);315 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});316 317 const collectionId = collection.collectionId;318 319 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;320 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});321 });322 323 });324325 it('will set pallet address as confirmed admin for collection with confirmed sponsor', async () => {326 // arrange: Charlie creates Punks327 // arrange: setCollectionSponsor(Punks.Id, Dave)328 // arrange: confirmSponsorship(Punks.Id, Dave) /// Dave is confirmed sponsor329330 // act: Admin calls appPromotion.sponsorCollection(Punks.id)331332 // assert: query collectionById: Punks sponsoring is confirmed by PalleteAddress333 334 await usingPlaygrounds(async (helper) => {335 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});336 await collection.setSponsor(alice, bob.address);337 338 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});339 expect(await collection.confirmSponsorship(bob)).to.be.true;340 341 const collectionId = collection.collectionId;342 343 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;344 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});345 });346 });347348 it('can be overwritten by collection owner', async () => {349 // arrange: Charlie creates Punks350 // arrange: appPromotion.sponsorCollection(Punks.Id) /// Sponsor of Punks is pallete351352 // act: Charlie calls unique.setCollectionLimits(limits) /// Charlie as owner can successfully change limits353 // assert: query collectionById(Punks.id) 1. sponsored by pallete, 2. limits has been changed354355 // act: Charlie calls setCollectionSponsor(Dave) /// Collection owner reasignes sponsoring356 // assert: query collectionById: Punks sponsoring is unconfirmed by Dave357 358 await usingPlaygrounds(async (helper) => {359 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});360 const collectionId = collection.collectionId;361 362 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;363 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});364 365 expect(await collection.setLimits(alice, {sponsorTransferTimeout: 0})).to.be.true;366 expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);367 368 expect((await collection.setSponsor(alice, bob.address))).to.be.true;369 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});370 });371 372 });373 374 it('will keep collection limits set by the owner earlier', async () => {375 // arrange: const limits = {...all possible collection limits}376 // arrange: Charlie creates Punks377 // arrange: Charlie calls unique.setCollectionLimits(limits) /// Owner sets all possible limits378379 // act: Admin calls appPromotion.sponsorCollection(Punks.id)380 // assert: query collectionById(Punks.id) returns limits381 382 await usingPlaygrounds(async (helper) => {383 384 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});385 expect(await collection.setLimits(alice, {sponsorTransferTimeout: 0})).to.be.true;386 const limits = (await collection.getData())?.raw.limits;387 388 const collectionId = collection.collectionId;389 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;390 expect((await collection.getData())?.raw.limits).to.be.deep.equal(limits);391 });392 393 });394 395 it('will throw if collection doesn\'t exist', async () => {396 // assert: Admin calls appPromotion.sponsorCollection(999999999999999) throw 397 await usingPlaygrounds(async (helper) => {398 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(999999999))).to.be.eventually.rejected;399 });400 });401402 it('will throw if collection was burnt', async () => {403 // arrange: Charlie creates Punks404 // arrange: Charlie burns Punks405406 // assert: Admin calls appPromotion.sponsorCollection(Punks.id) throw407 408 await usingPlaygrounds(async (helper) => {409 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});410 const collectionId = collection.collectionId;411 412 expect((await collection.burn(alice))).to.be.true;413 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.rejected;414 });415 416 });417});418419420describe('app-promotion stopSponsoringCollection', () => {421 before(async function () {422 await usingPlaygrounds(async (helper, privateKeyWrapper) => {423 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();424 alice = privateKeyWrapper('//Alice');425 bob = privateKeyWrapper('//Bob');426 palletAdmin = privateKeyWrapper('//palletAdmin');427 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());428 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());429 430 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));431 await helper.signTransaction(alice, tx);432433 nominal = helper.balance.getOneTokenNominal();434 });435 });436 437 it('can not be called by non-admin', async () => {438 // arrange: Alice creates Punks439 // arrange: appPromotion.sponsorCollection(Punks.Id)440441 // assert: Random calls appPromotion.stopSponsoringCollection(Punks) throws442 // assert: query collectionById(Punks.id): sponsoring confirmed by PalleteAddress443 444 await usingPlaygrounds(async (helper) => {445 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});446 const collectionId = collection.collectionId;447 448 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;449 450 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;451 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});452 });453 });454455 it('will set sponsoring as disabled', async () => {456 // arrange: Alice creates Punks457 // arrange: appPromotion.sponsorCollection(Punks.Id)458459 // act: Admin calls appPromotion.stopSponsoringCollection(Punks)460461 // assert: query collectionById(Punks.id): sponsoring unconfirmed462 463 await usingPlaygrounds(async (helper) => {464 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});465 const collectionId = collection.collectionId;466 467 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;468 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.fulfilled;469 470 expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');471 });472 });473474 it('will not affect collection which is not sponsored by pallete', async () => {475 // arrange: Alice creates Punks476 // arrange: Alice calls setCollectionSponsoring(Punks)477 // arrange: Alice calls confirmSponsorship(Punks)478479 // act: Admin calls appPromotion.stopSponsoringCollection(A)480 // assert: query collectionById(Punks): Sponsoring: {Confirmed: Alice} /// Alice still collection owner481 482 await usingPlaygrounds(async (helper) => {483 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});484 const collectionId = collection.collectionId;485 expect(await collection.setSponsor(alice, alice.address)).to.be.true;486 expect(await collection.confirmSponsorship(alice)).to.be.true;487 488 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;489 490 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: alice.address});491 });492 493 });494495 it('will throw if collection does not exist', async () => {496 // arrange: Alice creates Punks497 // arrange: Alice burns Punks498499 // assert: Admin calls appPromotion.stopSponsoringCollection(Punks.id) throws500 // assert: Admin calls appPromotion.stopSponsoringCollection(999999999999999) throw501 502 await usingPlaygrounds(async (helper) => {503 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});504 const collectionId = collection.collectionId;505 506 expect((await collection.burn(alice))).to.be.true;507 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;508 });509 });510});511512describe('app-promotion contract sponsoring', () => {513 it('will set contract sponsoring mode and set palletes address as a sponsor', async () => {514 // arrange: Alice deploys Flipper515 516 // act: Admin calls appPromotion.sponsorContract(Flipper.address)517518 // assert: contract.sponsoringMode = TODO519 // assert: contract.sponsor to be PalleteAddress520 });521522 it('will overwrite sponsoring mode and existed sponsor', async () => {523 // arrange: Alice deploys Flipper524 // arrange: Alice sets self sponsoring for Flipper525526 // act: Admin calls appPromotion.sponsorContract(Flipper.address)527528 // assert: contract.sponsoringMode = TODO529 // assert: contract.sponsor to be PalleteAddress530 });531532 it('can be overwritten by contract owner', async () => {533 // arrange: Alice deploys Flipper534 // arrange: Admin calls appPromotion.sponsorContract(Flipper.address)535536 // act: Alice sets self sponsoring for Flipper537538 // assert: contract.sponsoringMode = Self539 // assert: contract.sponsor to be contract540 });541542 it('can not be set by non admin', async () => {543 // arrange: Alice deploys Flipper544 // arrange: Alice sets self sponsoring for Flipper545546 // assert: Random calls appPromotion.sponsorContract(Flipper.address) throws547 // assert: contract.sponsoringMode = Self548 // assert: contract.sponsor to be contract549 });550551 it('will return unused gas fee to app-promotion pallete', async () => {552 // arrange: Alice deploys Flipper553 // arrange: Admin calls appPromotion.sponsorContract(Flipper.address)554555 // assert: Bob calls Flipper - expect balances deposit event do not appears for Bob /// Unused gas fee returns to contract556 // assert: Bobs balance the same557 });558559 it('will failed for non contract address', async () => {560 // arrange: web3 creates new address - 0x0561562 // assert: Admin calls appPromotion.sponsorContract(0x0) throws563 // assert: Admin calls appPromotion.sponsorContract(Substrate address) throws564 });565566 it('will actually sponsor transactions', async () => {567 // TODO test it because this is a new way of contract sponsoring568 });569});570571describe('app-promotion stopSponsoringContract', () => {572 before(async function () {573 await usingPlaygrounds(async (helper, privateKeyWrapper) => {574 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();575 alice = privateKeyWrapper('//Alice');576 bob = privateKeyWrapper('//Bob');577 palletAdmin = privateKeyWrapper('//palletAdmin');578 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());579 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());580 581 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));582 await helper.signTransaction(alice, tx);583 584 nominal = helper.balance.getOneTokenNominal();585 });586 });587 588 it('will set contract sponsoring mode as disabled', async () => {589 // arrange: Alice deploys Flipper590 // arrange: Admin calls appPromotion.sponsorContract(Flipper.address)591 592 // act: Admin calls appPromotion.stopSponsoringContract(Flipper.address)593 // assert: contract sponsoring mode = TODO594595 // act: Bob calls Flipper596597 // assert: PalleteAddress balance did not change598 // assert: Bobs balance less than before /// Bob payed some fee599 });600601 it('can not be called by non-admin', async () => {602 // arrange: Alice deploys Flipper603 // arrange: Admin calls appPromotion.sponsorContract(Flipper.address)604605 // assert: Random calls appPromotion.stopSponsoringContract(Flipper.address) throws606 // assert: contract sponsor is PallereAddress607 });608609 it('will not affect a contract which is not sponsored by pallete', async () => {610 // arrange: Alice deploys Flipper611 // arrange: Alice sets self sponsoring for Flipper612 613 // act: Admin calls appPromotion.stopSponsoringContract(Flipper.address) throws614615 // assert: contract.sponsoringMode = Self616 // assert: contract.sponsor to be contract617 });618619 it('will failed for non contract address', async () => {620 // arrange: web3 creates new address - 0x0621622 // expect stopSponsoringContract(0x0) throws623 });624});625626describe('app-promotion rewards', () => {627 const DAY = 7200n;628 629 630 before(async function () {631 await usingPlaygrounds(async (helper, privateKeyWrapper) => {632 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();633 alice = privateKeyWrapper('//Alice');634 bob = privateKeyWrapper('//Bob');635 palletAdmin = privateKeyWrapper('//palletAdmin');636 if (promotionStartBlock == null) {637 promotionStartBlock = (await helper.api!.query.parachainSystem.lastRelayChainBlockNumber()).toNumber();638 }639 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));640 await helper.signTransaction(alice, tx);641 642 const txStart = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.startAppPromotion(promotionStartBlock!));643 await helper.signTransaction(alice, txStart);644645 nominal = helper.balance.getOneTokenNominal();646 });647 });648649 after(async function () {650 await usingPlaygrounds(async (helper) => {651 await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.stopAppPromotion()));652 });653 });654 655 it('will credit 0.05% for staking period', async () => {656 // arrange: bob.stake(10000);657 // arrange: bob.stake(20000);658 // arrange: waitForRewards();659660 // assert: bob.staked to equal [10005, 20010]661 662 await usingPlaygrounds(async helper => {663 const staker = await createUser(50n * nominal);664 await waitForRecalculationBlock(helper.api!);665 666 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(1n * nominal))).to.be.eventually.fulfilled;667 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(2n * nominal))).to.be.eventually.fulfilled;668 await waitForRelayBlock(helper.api!, 36);669 670 671 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))672 .map(([_, amount]) => amount.toBigInt()))673 .to.be.deep.equal([calculateIncome(nominal, 10n), calculateIncome(2n * nominal, 10n)]);674 });675 676 });677 678 it('will not be credited for unstaked (reserved) balance', async () => {679 // arrange: bob.stake(10000);680 // arrange: bob.unstake(5000);681 // arrange: waitForRewards();682683 // assert: bob.staked to equal [5002.5]684 await usingPlaygrounds(async helper => {685 const staker = await createUser(20n * nominal);686 await waitForRecalculationBlock(helper.api!);687 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;688 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(5n * nominal))).to.be.eventually.fulfilled;689 await waitForRelayBlock(helper.api!, 38);690691 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))692 .map(([_, amount]) => amount.toBigInt()))693 .to.be.deep.equal([calculateIncome(5n * nominal, 10n)]);694 695 });696 697 });698 699 it('will bring compound interest', async () => {700 // arrange: bob balance = 30000701 // arrange: bob.stake(10000);702 // arrange: bob.stake(10000);703 // arrange: waitForRewards();704705 // assert: bob.staked() equal [10005, 10005, 10005] /// 10_000 * 1.0005706 // act: waitForRewards();707708 // assert: bob.staked() equal [10010.0025, 10010.0025, 10010.0025] /// 10_005 * 1.0005709 // act: bob.unstake(10.0025)710 // assert: bob.staked() equal [10000, 10010.0025, 10010.0025] /// 10_005 * 1.0005711712 // act: waitForRewards();713 // assert: bob.staked() equal [10005, 10015,00750125, 10015,00750125] ///714 await usingPlaygrounds(async helper => {715 const staker = await createUser(40n * nominal);716 717 await waitForRecalculationBlock(helper.api!);718 719720 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;721 722 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;723 724 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;725 726 await waitForRelayBlock(helper.api!, 34);727 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))728 .map(([_, amount]) => amount.toBigInt()))729 .to.be.deep.equal([calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n)]);730 731 await waitForRelayBlock(helper.api!, 20);732 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(calculateIncome(10n * nominal, 10n, 2) - 10n * nominal))).to.be.eventually.fulfilled;733 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))734 .map(([_, amount]) => amount.toBigInt()))735 .to.be.deep.equal([10n * nominal, calculateIncome(10n * nominal, 10n, 2), calculateIncome(10n * nominal, 10n, 2)]);736 });737 738 });739});740741742function waitForRecalculationBlock(api: ApiPromise): Promise<void> {743 return new Promise<void>(async (resolve, reject) => {744 const unsubscribe = await api.query.system.events((events) => {745 746 events.forEach((record) => {747 748 const {event, phase} = record;749 const types = event.typeDef;750 751 if (event.section === 'promotion' && event.method === 'StakingRecalculation') {752 unsubscribe();753 resolve();754 }755 });756 });757 });758}759760async function waitForRelayBlock(api: ApiPromise, blocks = 1): Promise<void> {761 const current_block = (await api.query.parachainSystem.lastRelayChainBlockNumber()).toNumber();762 return new Promise<void>(async (resolve, reject) => {763 const unsubscribe = await api.query.parachainSystem.validationData(async (data) => {764 // console.log(`${current_block} || ${data.value.relayParentNumber.toNumber()}`);765 if (data.value.relayParentNumber.toNumber() - current_block >= blocks) {766 unsubscribe();767 resolve();768 }769 });770 });771 772}773774775function calculatePalleteAddress(palletId: any) {776 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));777 return encodeAddress(address);778}779function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint {780 const DAY = 7200n;781 const ACCURACY = 1_000_000_000n;782 const income = base + base * (ACCURACY * (calcPeriod * 5n) / (10_000n * DAY)) / ACCURACY ;783 784 if (iter > 1) {785 return calculateIncome(income, calcPeriod, iter - 1);786 } else return income;787}788789async function createUser(amount?: bigint) {790 return await usingPlaygrounds(async helper => {791 const user: IKeyringPair = helper.util.fromSeed(mnemonicGenerate());792 await helper.balance.transferToSubstrate(alice, user.address, amount ? amount : 10n * helper.balance.getOneTokenNominal());793 return user;794 });795}