1234567891011121314151617import {default as usingApi, submitTransactionAsync} from './substrate/substrate-api';18import {IKeyringPair, ITuple} from '@polkadot/types/types';19import {20 21 createMultipleItemsExpectSuccess,22 isTokenExists,23 getLastTokenId,24 getAllowance,25 approve,26 transferFrom,27 createCollection,28 transfer,29 burnItem,30 normalizeAccountId,31 CrossAccountId,32 createFungibleItemExpectSuccess,33 U128_MAX,34 burnFromExpectSuccess,35 UNIQUE,36 getModuleNames,37 Pallets,38 getBlockNumber,39} from './util/helpers';4041import chai, {use} from 'chai';42import chaiAsPromised from 'chai-as-promised';43import {usingPlaygrounds} from './util/playgrounds';44import {default as waitNewBlocks} from './substrate/wait-new-blocks';4546import {encodeAddress, hdEthereum, mnemonicGenerate} from '@polkadot/util-crypto';47import {stringToU8a} from '@polkadot/util';48import {UniqueHelper} from './util/playgrounds/unique';49import {ApiPromise} from '@polkadot/api';50chai.use(chaiAsPromised);51const expect = chai.expect;5253let alice: IKeyringPair;54let bob: IKeyringPair;55let palletAdmin: IKeyringPair;56let nominal: bigint;57let promotionStartBlock: number | null = null;5859describe('app-promotions.stake extrinsic', () => {60 before(async function() {61 await usingPlaygrounds(async (helper, privateKeyWrapper) => {62 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();63 alice = privateKeyWrapper('//Alice');64 bob = privateKeyWrapper('//Bob');65 palletAdmin = privateKeyWrapper('//palletAdmin');66 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));67 nominal = helper.balance.getOneTokenNominal();68 await helper.signTransaction(alice, tx);69 });70 });71 it('will change balance state to "locked", add it to "staked" map, and increase "totalStaked" amount', async () => {72 73 74 75 76 77 78 79 80 81 82 83 84 await usingPlaygrounds(async (helper, privateKeyWrapper) => {85 const totalStakedBefore = (await helper.api!.rpc.unique.totalStaked()).toBigInt();86 const staker = await createUser();87 88 89 90 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(nominal / 2n))).to.be.eventually.rejected;91 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(1n * nominal))).to.be.eventually.fulfilled;92 expect((await helper.api!.rpc.unique.totalStakingLocked(normalizeAccountId(staker))).toBigInt()).to.be.equal(nominal);93 expect(9n * nominal - await helper.balance.getSubstrate(staker.address) <= nominal / 2n).to.be.true;94 expect((await helper.api!.rpc.unique.totalStaked(normalizeAccountId(staker))).toBigInt()).to.be.equal(nominal);95 expect((await helper.api!.rpc.unique.totalStaked()).toBigInt()).to.be.equal(totalStakedBefore + nominal);96 97 await waitNewBlocks(helper.api!, 1);98 99 100 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(2n * nominal))).to.be.eventually.fulfilled;101 expect((await helper.api!.rpc.unique.totalStakingLocked(normalizeAccountId(staker))).toBigInt()).to.be.equal(3n * nominal);102 103 const stakedPerBlock = (await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([block, amount]) => [block.toBigInt(), amount.toBigInt()]);104 expect(stakedPerBlock.map((x) => x[1])).to.be.deep.equal([nominal, 2n * nominal]);105 });106 });107 108 it('will throws if stake amount is more than total free balance', async () => {109 110 111112 113 114115 await usingPlaygrounds(async helper => { 116 117 const staker = await createUser();118 119 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.rejected;120 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(7n * nominal))).to.be.eventually.fulfilled;121 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(4n * nominal))).to.be.eventually.rejected;122 123 });124 });125 126 it('for different accounts in one block is possible', async () => {127 128 129130 131 await usingPlaygrounds(async helper => {132 const crowd = [];133 for(let i = 4; i--;) crowd.push(await createUser());134 135 const promises = crowd.map(async user => helper.signTransaction(user, helper.api!.tx.promotion.stake(nominal)));136 await expect(Promise.all(promises)).to.be.eventually.fulfilled;137 138 for (let i = 0; i < crowd.length; i++){139 expect((await helper.api!.rpc.unique.totalStaked(normalizeAccountId(crowd[i]))).toBigInt()).to.be.equal(nominal); 140 }141 });142 });143144});145146describe('unstake balance extrinsic', () => {147 before(async function () {148 await usingPlaygrounds(async (helper, privateKeyWrapper) => {149 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();150 alice = privateKeyWrapper('//Alice');151 bob = privateKeyWrapper('//Bob');152 palletAdmin = privateKeyWrapper('//palletAdmin');153 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));154 nominal = helper.balance.getOneTokenNominal();155 await helper.signTransaction(alice, tx);156 });157 });158 159 it('will change balance state to "reserved", add it to "pendingUnstake" map, and subtract it from totalStaked', async () => {160 161 162163 164 165 166 167 168 await usingPlaygrounds(async helper => {169 const totalStakedBefore = (await helper.api!.rpc.unique.totalStaked()).toBigInt();170 const staker = await createUser();171 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(5n * nominal))).to.be.eventually.fulfilled;172 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(3n * nominal))).to.be.eventually.fulfilled;173 expect((await helper.api!.rpc.unique.pendingUnstake(normalizeAccountId(staker.address))).toBigInt()).to.be.equal(3n * nominal);174 expect((await helper.api!.rpc.unique.totalStaked(normalizeAccountId(staker))).toBigInt()).to.be.equal(2n * nominal);175 expect((await helper.api!.rpc.unique.totalStaked()).toBigInt()).to.be.equal(totalStakedBefore + 2n * nominal);176 });177 });178 it('will remove from the "staked" map starting from the oldest entry', async () => {179 180 181 182 183184 185186187 188 189 190191 192 193 194195 196 197 198 await usingPlaygrounds(async helper => {199 const totalStakedBefore = (await helper.api!.rpc.unique.totalStaked()).toBigInt();200 const staker = await createUser();201 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(1n * nominal))).to.be.eventually.fulfilled;202 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(2n * nominal))).to.be.eventually.fulfilled;203 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(3n * nominal))).to.be.eventually.fulfilled;204 let stakedPerBlock = (await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt());205 expect(stakedPerBlock).to.be.deep.equal([nominal, 2n * nominal, 3n * nominal]);206 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(3n * nominal / 10n))).to.be.eventually.fulfilled;207 expect((await helper.api!.rpc.unique.pendingUnstake(normalizeAccountId(staker.address))).toBigInt()).to.be.equal(3n * nominal / 10n);208 stakedPerBlock = (await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt());209 210 expect(stakedPerBlock).to.be.deep.equal([7n * nominal / 10n, 2n * nominal, 3n * nominal]);211 212 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(17n * nominal / 10n))).to.be.eventually.fulfilled;213 stakedPerBlock = (await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt());214 expect(stakedPerBlock).to.be.deep.equal([nominal, 3n * nominal]);215 const unstakedPerBlock = (await helper.api!.rpc.unique.pendingUnstakePerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt());216 217 expect(unstakedPerBlock).to.be.deep.equal([3n * nominal / 10n, 17n * nominal / 10n]);218 219 await waitNewBlocks(helper.api!, 1);220 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(4n * nominal))).to.be.eventually.fulfilled;221 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt())).to.be.deep.equal([]);222 expect((await helper.api!.rpc.unique.pendingUnstakePerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt())).to.be.deep.equal([3n * nominal / 10n, 17n * nominal / 10n, 4n * nominal]);223 });224 225 });226});227228229230describe('Admin adress', () => {231 before(async function () {232 await usingPlaygrounds(async (helper, privateKeyWrapper) => {233 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();234 alice = privateKeyWrapper('//Alice');235 bob = privateKeyWrapper('//Bob');236 palletAdmin = privateKeyWrapper('//palletAdmin');237 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());238 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));239 240 await helper.signTransaction(alice, tx);241 242 nominal = helper.balance.getOneTokenNominal();243 });244 });245246 it('can be set by sudo only', async () => {247 248 249 250 await usingPlaygrounds(async (helper) => {251 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(alice))))).to.be.eventually.fulfilled;252 await expect(helper.signTransaction(bob, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.rejected;253 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.fulfilled;254 });255 256 });257 258 it('can be any valid CrossAccountId', async () => {259 260 261262 263 264 265 266 267 268 await usingPlaygrounds(async (helper) => {269 270 const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};271 272 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(ethAcc)))).to.be.eventually.fulfilled;273 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin))))).to.be.eventually.fulfilled;274 275 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});276 277 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;278 });279 280 });281282 it('can be reassigned', async () => {283 284 285 286287 288 289290 291 292 293 await usingPlaygrounds(async (helper) => {294 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});295 296 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(alice))))).to.be.eventually.fulfilled;297 await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(bob))))).to.be.eventually.fulfilled;298 await expect(helper.signTransaction(alice, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.rejected;299 300 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.sponsorCollection(collection.collectionId))).to.be.eventually.fulfilled;301 });302 303 });304305});306307describe('App-promotion collection sponsoring', () => {308 before(async function () {309 await usingPlaygrounds(async (helper, privateKeyWrapper) => {310 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();311 alice = privateKeyWrapper('//Alice');312 bob = privateKeyWrapper('//Bob');313 palletAdmin = privateKeyWrapper('//palletAdmin');314 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());315 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());316 317 318 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));319 await helper.signTransaction(alice, tx);320 321 322 323 324 nominal = helper.balance.getOneTokenNominal();325 });326 });327 328 it('can not be set by non admin', async () => {329 330 331 332 333334 335 336 337 await usingPlaygrounds(async (helper) => {338 const colletcion = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});339 340 const collectionId = colletcion.collectionId;341 342 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.rejected;343 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;344 });345 346 });347348 it('will set pallet address as confirmed admin for collection without sponsor', async () => {349 350351 352353 354 355 await usingPlaygrounds(async (helper) => {356 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});357 const collectionId = collection.collectionId;358359 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;360 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});361 });362 363 });364365 it('will set pallet address as confirmed admin for collection with unconfirmed sponsor', async () => {366 367 368369 370371 372 373 await usingPlaygrounds(async (helper) => {374 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});375 await collection.setSponsor(alice, bob.address);376 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});377 378 const collectionId = collection.collectionId;379 380 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;381 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});382 });383 384 });385386 it('will set pallet address as confirmed admin for collection with confirmed sponsor', async () => {387 388 389 390391 392393 394 395 await usingPlaygrounds(async (helper) => {396 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});397 await collection.setSponsor(alice, bob.address);398 399 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});400 expect(await collection.confirmSponsorship(bob)).to.be.true;401 402 const collectionId = collection.collectionId;403 404 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;405 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});406 });407 });408409 it('can be overwritten by collection owner', async () => {410 411 412413 414 415416 417 418 419 await usingPlaygrounds(async (helper) => {420 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});421 const collectionId = collection.collectionId;422 423 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;424 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});425 426 expect(await collection.setLimits(alice, {sponsorTransferTimeout: 0})).to.be.true;427 expect((await collection.getData())?.raw.limits.sponsorTransferTimeout).to.be.equal(0);428 429 expect((await collection.setSponsor(alice, bob.address))).to.be.true;430 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: bob.address});431 });432 433 });434 435 it('will keep collection limits set by the owner earlier', async () => {436 437 438 439440 441 442 443 await usingPlaygrounds(async (helper) => {444 445 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});446 expect(await collection.setLimits(alice, {sponsorTransferTimeout: 0})).to.be.true;447 const limits = (await collection.getData())?.raw.limits;448 449 const collectionId = collection.collectionId;450 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;451 expect((await collection.getData())?.raw.limits).to.be.deep.equal(limits);452 });453 454 });455 456 it('will throw if collection doesn\'t exist', async () => {457 458 await usingPlaygrounds(async (helper) => {459 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(999999999))).to.be.eventually.rejected;460 });461 });462463 it('will throw if collection was burnt', async () => {464 465 466467 468 469 await usingPlaygrounds(async (helper) => {470 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});471 const collectionId = collection.collectionId;472 473 expect((await collection.burn(alice))).to.be.true;474 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.rejected;475 });476 477 });478});479480481describe('app-promotion stopSponsoringCollection', () => {482 before(async function () {483 await usingPlaygrounds(async (helper, privateKeyWrapper) => {484 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();485 alice = privateKeyWrapper('//Alice');486 bob = privateKeyWrapper('//Bob');487 palletAdmin = privateKeyWrapper('//palletAdmin');488 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());489 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());490 491 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));492 await helper.signTransaction(alice, tx);493494 nominal = helper.balance.getOneTokenNominal();495 });496 });497 498 it('can not be called by non-admin', async () => {499 500 501502 503 504 505 await usingPlaygrounds(async (helper) => {506 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});507 const collectionId = collection.collectionId;508 509 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;510 511 await expect(helper.signTransaction(bob, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;512 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: calculatePalleteAddress('appstake')});513 });514 });515516 it('will set sponsoring as disabled', async () => {517 518 519520 521522 523 524 await usingPlaygrounds(async (helper) => {525 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});526 const collectionId = collection.collectionId;527 528 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.sponsorCollection(collectionId))).to.be.eventually.fulfilled;529 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.fulfilled;530 531 expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled');532 });533 });534535 it('will not affect collection which is not sponsored by pallete', async () => {536 537 538 539540 541 542 543 await usingPlaygrounds(async (helper) => {544 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});545 const collectionId = collection.collectionId;546 expect(await collection.setSponsor(alice, alice.address)).to.be.true;547 expect(await collection.confirmSponsorship(alice)).to.be.true;548 549 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;550 551 expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: alice.address});552 });553 554 });555556 it('will throw if collection does not exist', async () => {557 558 559560 561 562 563 await usingPlaygrounds(async (helper) => {564 const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'});565 const collectionId = collection.collectionId;566 567 expect((await collection.burn(alice))).to.be.true;568 await expect(helper.signTransaction(palletAdmin, helper.api!.tx.promotion.stopSponsorignCollection(collectionId))).to.be.eventually.rejected;569 });570 });571});572573describe('app-promotion contract sponsoring', () => {574 it('will set contract sponsoring mode and set palletes address as a sponsor', async () => {575 576 577 578579 580 581 });582583 it('will overwrite sponsoring mode and existed sponsor', async () => {584 585 586587 588589 590 591 });592593 it('can be overwritten by contract owner', async () => {594 595 596597 598599 600 601 });602603 it('can not be set by non admin', async () => {604 605 606607 608 609 610 });611612 it('will return unused gas fee to app-promotion pallete', async () => {613 614 615616 617 618 });619620 it('will failed for non contract address', async () => {621 622623 624 625 });626627 it('will actually sponsor transactions', async () => {628 629 });630});631632describe('app-promotion stopSponsoringContract', () => {633 before(async function () {634 await usingPlaygrounds(async (helper, privateKeyWrapper) => {635 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();636 alice = privateKeyWrapper('//Alice');637 bob = privateKeyWrapper('//Bob');638 palletAdmin = privateKeyWrapper('//palletAdmin');639 await helper.balance.transferToSubstrate(alice, palletAdmin.address, 10n * helper.balance.getOneTokenNominal());640 await helper.balance.transferToSubstrate(alice, calculatePalleteAddress('appstake'), 10n * helper.balance.getOneTokenNominal());641 642 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));643 await helper.signTransaction(alice, tx);644 645 nominal = helper.balance.getOneTokenNominal();646 });647 });648 649 it('will set contract sponsoring mode as disabled', async () => {650 651 652 653 654 655656 657658 659 660 });661662 it('can not be called by non-admin', async () => {663 664 665666 667 668 });669670 it('will not affect a contract which is not sponsored by pallete', async () => {671 672 673 674 675676 677 678 });679680 it('will failed for non contract address', async () => {681 682683 684 });685});686687describe('app-promotion rewards', () => {688 const DAY = 7200n;689 690 691 before(async function () {692 await usingPlaygrounds(async (helper, privateKeyWrapper) => {693 if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip();694 alice = privateKeyWrapper('//Alice');695 bob = privateKeyWrapper('//Bob');696 palletAdmin = privateKeyWrapper('//palletAdmin');697 if (promotionStartBlock == null) {698 promotionStartBlock = (await helper.api!.query.parachainSystem.lastRelayChainBlockNumber()).toNumber();699 }700 const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.setAdminAddress(normalizeAccountId(palletAdmin)));701 await helper.signTransaction(alice, tx);702 703 const txStart = helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.startAppPromotion(promotionStartBlock!));704 await helper.signTransaction(alice, txStart);705706 nominal = helper.balance.getOneTokenNominal();707 });708 });709710 after(async function () {711 await usingPlaygrounds(async (helper) => {712 await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.stopAppPromotion()));713 });714 });715 716 it('will credit 0.05% for staking period', async () => {717 718 719 720721 722 723 await usingPlaygrounds(async helper => {724 const staker = await createUser(50n * nominal);725 await waitForRecalculationBlock(helper.api!);726 727 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(1n * nominal))).to.be.eventually.fulfilled;728 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(2n * nominal))).to.be.eventually.fulfilled;729 await waitForRelayBlock(helper.api!, 36);730 731 732 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))733 .map(([_, amount]) => amount.toBigInt()))734 .to.be.deep.equal([calculateIncome(nominal, 10n), calculateIncome(2n * nominal, 10n)]);735 });736 737 });738 739 it('will not be credited for unstaked (reserved) balance', async () => {740 741 742 743744 745 await usingPlaygrounds(async helper => {746 const staker = await createUser(20n * nominal);747 await waitForRecalculationBlock(helper.api!);748 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;749 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(5n * nominal))).to.be.eventually.fulfilled;750 await waitForRelayBlock(helper.api!, 38);751752 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))753 .map(([_, amount]) => amount.toBigInt()))754 .to.be.deep.equal([calculateIncome(5n * nominal, 10n)]);755 756 });757 758 });759 760 it('will bring compound interest', async () => {761 762 763 764 765766 767 768769 770 771 772773 774 775 await usingPlaygrounds(async helper => {776 const staker = await createUser(40n * nominal);777 778 await waitForRecalculationBlock(helper.api!);779 780781 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;782 783 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;784 785 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;786 787 await waitForRelayBlock(helper.api!, 34);788 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))789 .map(([_, amount]) => amount.toBigInt()))790 .to.be.deep.equal([calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n)]);791 792 await waitForRelayBlock(helper.api!, 20);793 await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(calculateIncome(10n * nominal, 10n, 2) - 10n * nominal))).to.be.eventually.fulfilled;794 expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))795 .map(([_, amount]) => amount.toBigInt()))796 .to.be.deep.equal([10n * nominal, calculateIncome(10n * nominal, 10n, 2), calculateIncome(10n * nominal, 10n, 2)]);797 });798 799 });800});801802803function waitForRecalculationBlock(api: ApiPromise): Promise<void> {804 return new Promise<void>(async (resolve, reject) => {805 const unsubscribe = await api.query.system.events((events) => {806 807 events.forEach((record) => {808 809 const {event, phase} = record;810 const types = event.typeDef;811 812 if (event.section === 'promotion' && event.method === 'StakingRecalculation') {813 unsubscribe();814 resolve();815 }816 });817 });818 });819}820821async function waitForRelayBlock(api: ApiPromise, blocks = 1): Promise<void> {822 const current_block = (await api.query.parachainSystem.lastRelayChainBlockNumber()).toNumber();823 return new Promise<void>(async (resolve, reject) => {824 const unsubscribe = await api.query.parachainSystem.validationData(async (data) => {825 826 if (data.value.relayParentNumber.toNumber() - current_block >= blocks) {827 unsubscribe();828 resolve();829 }830 });831 });832 833}834835836function calculatePalleteAddress(palletId: any) {837 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));838 return encodeAddress(address);839}840function calculateIncome(base: bigint, calcPeriod: bigint, iter = 0): bigint {841 const DAY = 7200n;842 const ACCURACY = 1_000_000_000n;843 const income = base + base * (ACCURACY * (calcPeriod * 5n) / (10_000n * DAY)) / ACCURACY ;844 845 if (iter > 1) {846 return calculateIncome(income, calcPeriod, iter - 1);847 } else return income;848}849850async function createUser(amount?: bigint) {851 return await usingPlaygrounds(async helper => {852 const user: IKeyringPair = helper.util.fromSeed(mnemonicGenerate());853 await helper.balance.transferToSubstrate(alice, user.address, amount ? amount : 10n * helper.balance.getOneTokenNominal());854 return user;855 });856}857858const creteAccounts = async (balances: bigint[], donor: IKeyringPair, helper: UniqueHelper) => {859 let nonce = await helper.chain.getNonce(donor.address);860 const tokenNominal = helper.balance.getOneTokenNominal();861 const transactions = [];862 const accounts = [];863 for (const balance of balances) {864 const recepient = helper.util.fromSeed(mnemonicGenerate());865 accounts.push(recepient);866 if (balance !== 0n){867 const tx = helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, balance * tokenNominal]);868 transactions.push(helper.signTransaction(donor, tx, 'account generation', {nonce}));869 nonce++;870 }871 }872873 await Promise.all(transactions);874 return accounts;875};