git.delta.rocks / unique-network / refs/commits / 591c0ace12a9

difftreelog

fix use wait.forParachainBlockNumber

Daniel Shiposha2022-11-08parent: #96d265c.patch.diff
in: master

1 file changed

modifiedtests/src/scheduler.seqtest.tsdiffbeforeafterboth
before · tests/src/scheduler.seqtest.ts
1// 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 {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {DevUniqueHelper} from './util/playgrounds/unique.dev';2021describe('Scheduling token and balance transfers', () => {22  let superuser: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;25  let charlie: IKeyringPair;2627  before(async function() {28    await usingPlaygrounds(async (helper, privateKey) => {29      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);3031      superuser = await privateKey('//Alice');32      const donor = await privateKey({filename: __filename});33      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);3435      await helper.testUtils.enable();36    });37  });3839  itSub('Can delay a transfer of an owned token', async ({helper}) => {40    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});41    const token = await collection.mintToken(alice);42    const schedulerId = await helper.arrange.makeScheduledId();43    const blocksBeforeExecution = 4;4445    await token.scheduleAfter(schedulerId, blocksBeforeExecution)46      .transfer(alice, {Substrate: bob.address});4748    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});4950    await helper.wait.newBlocks(blocksBeforeExecution + 1);5152    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});53  });5455  itSub('Can transfer funds periodically', async ({helper}) => {56    const scheduledId = await helper.arrange.makeScheduledId();57    const waitForBlocks = 1;5859    const amount = 1n * helper.balance.getOneTokenNominal();60    const periodic = {61      period: 2,62      repetitions: 2,63    };6465    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);6667    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})68      .balance.transferToSubstrate(alice, bob.address, amount);6970    await helper.wait.newBlocks(waitForBlocks + 1);7172    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);73    expect(bobsBalanceAfterFirst)74      .to.be.equal(75        bobsBalanceBefore + 1n * amount,76        '#1 Balance of the recipient should be increased by 1 * amount',77      );7879    await helper.wait.newBlocks(periodic.period);8081    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);82    expect(bobsBalanceAfterSecond)83      .to.be.equal(84        bobsBalanceBefore + 2n * amount,85        '#2 Balance of the recipient should be increased by 2 * amount',86      );87  });8889  itSub('Can cancel a scheduled operation which has not yet taken effect', async ({helper}) => {90    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});91    const token = await collection.mintToken(alice);9293    const scheduledId = await helper.arrange.makeScheduledId();94    const waitForBlocks = 4;9596    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});9798    await token.scheduleAfter(scheduledId, waitForBlocks)99      .transfer(alice, {Substrate: bob.address});100101    await helper.scheduler.cancelScheduled(alice, scheduledId);102103    await helper.wait.newBlocks(waitForBlocks + 1);104105    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});106  });107108  itSub('Can cancel a periodic operation (transfer of funds)', async ({helper}) => {109    const waitForBlocks = 1;110    const periodic = {111      period: 3,112      repetitions: 2,113    };114115    const scheduledId = await helper.arrange.makeScheduledId();116117    const amount = 1n * helper.balance.getOneTokenNominal();118119    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);120121    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})122      .balance.transferToSubstrate(alice, bob.address, amount);123124    await helper.wait.newBlocks(waitForBlocks + 1);125126    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);127128    expect(bobsBalanceAfterFirst)129      .to.be.equal(130        bobsBalanceBefore + 1n * amount,131        '#1 Balance of the recipient should be increased by 1 * amount',132      );133134    await helper.scheduler.cancelScheduled(alice, scheduledId);135    await helper.wait.newBlocks(periodic.period);136137    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);138    expect(bobsBalanceAfterSecond)139      .to.be.equal(140        bobsBalanceAfterFirst,141        '#2 Balance of the recipient should not be changed',142      );143  });144145  itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.TestUtils], async ({helper}) => {146    const scheduledId = await helper.arrange.makeScheduledId();147    const waitForBlocks = 4;148149    const initTestVal = 42;150    const changedTestVal = 111;151152    await helper.testUtils.setTestValue(alice, initTestVal);153154    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks)155      .testUtils.setTestValueAndRollback(alice, changedTestVal);156157    await helper.wait.newBlocks(waitForBlocks + 1);158159    const testVal = await helper.testUtils.testValue();160    expect(testVal, 'The test value should NOT be commited')161      .to.be.equal(initTestVal);162  });163164  itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.TestUtils], async function({helper}) {165    const scheduledId = await helper.arrange.makeScheduledId();166    const waitForBlocks = 4;167    const periodic = {168      period: 2,169      repetitions: 2,170    };171172    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);173    const scheduledLen = dummyTx.callIndex.length;174175    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))176      .partialFee.toBigInt();177178    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})179      .testUtils.justTakeFee(alice);180181    await helper.wait.newBlocks(1);182183    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);184    let diff;185186    await helper.wait.newBlocks(waitForBlocks);187188    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);189    expect(190      aliceBalanceAfterFirst < aliceInitBalance,191      '[after execution #1] Scheduled task should take a fee',192    ).to.be.true;193194    diff = aliceInitBalance - aliceBalanceAfterFirst;195    expect(diff).to.be.equal(196      expectedScheduledFee,197      'Scheduled task should take the right amount of fees',198    );199200    await helper.wait.newBlocks(periodic.period);201202    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);203    expect(204      aliceBalanceAfterSecond < aliceBalanceAfterFirst,205      '[after execution #2] Scheduled task should take a fee',206    ).to.be.true;207208    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;209    expect(diff).to.be.equal(210      expectedScheduledFee,211      'Scheduled task should take the right amount of fees',212    );213  });214215  // Check if we can cancel a scheduled periodic operation216  // in the same block in which it is running217  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.TestUtils], async ({helper}) => {218    const currentBlockNumber = await helper.chain.getLatestBlockNumber();219    const blocksBeforeExecution = 10;220    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;221222    const [223      scheduledId,224      scheduledCancelId,225    ] = await helper.arrange.makeScheduledIds(2);226227    const periodic = {228      period: 5,229      repetitions: 5,230    };231232    const initTestVal = 0;233    const incTestVal = initTestVal + 1;234    const finalTestVal = initTestVal + 2;235236    await helper.testUtils.setTestValue(alice, initTestVal);237238    await helper.scheduler.scheduleAt<DevUniqueHelper>(scheduledId, firstExecutionBlockNumber, {periodic})239      .testUtils.incTestValue(alice);240241    // Cancel the inc tx after 2 executions242    // *in the same block* in which the second execution is scheduled243    await helper.scheduler.scheduleAt(244      scheduledCancelId,245      firstExecutionBlockNumber + periodic.period,246    ).scheduler.cancelScheduled(alice, scheduledId);247248    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);249250    // execution #0251    expect(await helper.testUtils.testValue())252      .to.be.equal(incTestVal);253254    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);255256    // execution #1257    expect(await helper.testUtils.testValue())258      .to.be.equal(finalTestVal);259260    for (let i = 1; i < periodic.repetitions; i++) {261      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));262      expect(await helper.testUtils.testValue())263        .to.be.equal(finalTestVal);264    }265  });266267  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.TestUtils], async ({helper}) => {268    const scheduledId = await helper.arrange.makeScheduledId();269    const waitForBlocks = 4;270    const periodic = {271      period: 2,272      repetitions: 5,273    };274275    const initTestVal = 0;276    const maxTestVal = 2;277278    await helper.testUtils.setTestValue(alice, initTestVal);279280    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})281      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);282283    await helper.wait.newBlocks(waitForBlocks + 1);284285    // execution #0286    expect(await helper.testUtils.testValue())287      .to.be.equal(initTestVal + 1);288289    await helper.wait.newBlocks(periodic.period);290291    // execution #1292    expect(await helper.testUtils.testValue())293      .to.be.equal(initTestVal + 2);294295    await helper.wait.newBlocks(periodic.period);296297    // <canceled>298    expect(await helper.testUtils.testValue())299      .to.be.equal(initTestVal + 2);300  });301302  itSub('Root can cancel any scheduled operation', async ({helper}) => {303    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});304    const token = await collection.mintToken(bob);305306    const scheduledId = await helper.arrange.makeScheduledId();307    const waitForBlocks = 4;308309    await token.scheduleAfter(scheduledId, waitForBlocks)310      .transfer(bob, {Substrate: alice.address});311312    await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);313314    await helper.wait.newBlocks(waitForBlocks + 1);315316    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});317  });318319  itSub('Root can set prioritized scheduled operation', async ({helper}) => {320    const scheduledId = await helper.arrange.makeScheduledId();321    const waitForBlocks = 4;322323    const amount = 42n * helper.balance.getOneTokenNominal();324325    const balanceBefore = await helper.balance.getSubstrate(charlie.address);326327    await helper.getSudo()328      .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42})329      .balance.forceTransferToSubstrate(superuser, bob.address, charlie.address, amount);330331    await helper.wait.newBlocks(waitForBlocks + 1);332333    const balanceAfter = await helper.balance.getSubstrate(charlie.address);334335    expect(balanceAfter > balanceBefore).to.be.true;336337    const diff = balanceAfter - balanceBefore;338    expect(diff).to.be.equal(amount);339  });340341  itSub("Root can change scheduled operation's priority", async ({helper}) => {342    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});343    const token = await collection.mintToken(bob);344345    const scheduledId = await helper.arrange.makeScheduledId();346    const waitForBlocks = 6;347348    await token.scheduleAfter(scheduledId, waitForBlocks)349      .transfer(bob, {Substrate: alice.address});350351    const priority = 112;352    await helper.getSudo().scheduler.changePriority(superuser, scheduledId, priority);353354    const priorityChanged = await helper.wait.event(355      waitForBlocks,356      'scheduler',357      'PriorityChanged',358    );359360    expect(priorityChanged !== null).to.be.true;361    expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString());362  });363364  itSub('Prioritized operations execute in valid order', async ({helper}) => {365    const [366      scheduledFirstId,367      scheduledSecondId,368    ] = await helper.arrange.makeScheduledIds(2);369370    const currentBlockNumber = await helper.chain.getLatestBlockNumber();371    const blocksBeforeExecution = 6;372    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;373374    const prioHigh = 0;375    const prioLow = 255;376377    const periodic = {378      period: 6,379      repetitions: 2,380    };381382    const amount = 1n * helper.balance.getOneTokenNominal();383384    // Scheduler a task with a lower priority first, then with a higher priority385    await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic})386      .balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);387388    await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic})389      .balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);390391    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');392393    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);394395    // Flip priorities396    await helper.getSudo().scheduler.changePriority(superuser, scheduledFirstId, prioHigh);397    await helper.getSudo().scheduler.changePriority(superuser, scheduledSecondId, prioLow);398399    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);400401    const dispatchEvents = capture.extractCapturedEvents();402    expect(dispatchEvents.length).to.be.equal(4);403404    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());405406    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];407    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];408409    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);410    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);411412    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);413    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);414  });415416  itSub('Periodic operations always can be rescheduled', async ({helper}) => {417    const maxScheduledPerBlock = 50;418    const numFilledBlocks = 3;419    const ids = await helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);420    const periodicId = ids[0];421    const fillIds = ids.slice(1);422423    const initTestVal = 0;424    const firstExecTestVal = 1;425    const secondExecTestVal = 2;426    await helper.testUtils.setTestValue(alice, initTestVal);427428    const currentBlockNumber = await helper.chain.getLatestBlockNumber();429    const blocksBeforeExecution = 8;430    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;431432    const period = 5;433434    const periodic = {435      period,436      repetitions: 2,437    };438439    // Fill `numFilledBlocks` blocks beginning from the block in which the second execution should occur440    const txs = [];441    for (let offset = 0; offset < numFilledBlocks; offset ++) {442      for (let i = 0; i < maxScheduledPerBlock; i++) {443444        const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);445446        const when = firstExecutionBlockNumber + period + offset;447        const tx = helper.constructApiCall('api.tx.scheduler.scheduleNamed', [fillIds[i + offset * maxScheduledPerBlock], when, null, null, scheduledTx]);448449        txs.push(tx);450      }451    }452    await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);453454    await helper.scheduler.scheduleAt<DevUniqueHelper>(periodicId, firstExecutionBlockNumber, {periodic})455      .testUtils.incTestValue(alice);456457    await helper.wait.newBlocks(blocksBeforeExecution);458    expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);459460    await helper.wait.newBlocks(period + numFilledBlocks);461462    // The periodic operation should be postponed by `numFilledBlocks`463    for (let i = 0; i < numFilledBlocks; i++) {464      expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);465    }466467    // After the `numFilledBlocks` the periodic operation will eventually be executed468    expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);469  });470471  itSub('scheduled operations does not change nonce', async ({helper}) => {472    const scheduledId = await helper.arrange.makeScheduledId();473    const blocksBeforeExecution = 4;474475    await helper.scheduler476      .scheduleAfter<DevUniqueHelper>(scheduledId, blocksBeforeExecution)477      .balance.transferToSubstrate(alice, bob.address, 1n);478479    const initNonce = await helper.chain.getNonce(alice.address);480481    await helper.wait.newBlocks(blocksBeforeExecution + 1);482483    const finalNonce = await helper.chain.getNonce(alice.address);484485    expect(initNonce).to.be.equal(finalNonce);486  });487});488489describe('Negative Test: Scheduling', () => {490  let alice: IKeyringPair;491  let bob: IKeyringPair;492493  before(async function() {494    await usingPlaygrounds(async (helper, privateKey) => {495      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);496497      const donor = await privateKey({filename: __filename});498      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);499500      await helper.testUtils.enable();501    });502  });503504  itSub("Can't overwrite a scheduled ID", async ({helper}) => {505    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});506    const token = await collection.mintToken(alice);507508    const scheduledId = await helper.arrange.makeScheduledId();509    const waitForBlocks = 4;510511    await token.scheduleAfter(scheduledId, waitForBlocks)512      .transfer(alice, {Substrate: bob.address});513514    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);515    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))516      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);517518    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);519520    await helper.wait.newBlocks(waitForBlocks + 1);521522    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);523524    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});525    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);526  });527528  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {529    const scheduledId = await helper.arrange.makeScheduledId();530    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))531      .to.be.rejectedWith(/scheduler\.NotFound/);532  });533534  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {535    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});536    const token = await collection.mintToken(alice);537538    const scheduledId = await helper.arrange.makeScheduledId();539    const waitForBlocks = 4;540541    await token.scheduleAfter(scheduledId, waitForBlocks)542      .transfer(alice, {Substrate: bob.address});543544    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))545      .to.be.rejectedWith(/BadOrigin/);546547    await helper.wait.newBlocks(waitForBlocks + 1);548549    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});550  });551552  itSub("Regular user can't set prioritized scheduled operation", async ({helper}) => {553    const scheduledId = await helper.arrange.makeScheduledId();554    const waitForBlocks = 4;555556    const amount = 42n * helper.balance.getOneTokenNominal();557558    const balanceBefore = await helper.balance.getSubstrate(bob.address);559560    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});561    562    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))563      .to.be.rejectedWith(/BadOrigin/);564565    await helper.wait.newBlocks(waitForBlocks + 1);566567    const balanceAfter = await helper.balance.getSubstrate(bob.address);568569    expect(balanceAfter).to.be.equal(balanceBefore);570  });571572  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {573    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});574    const token = await collection.mintToken(bob);575576    const scheduledId = await helper.arrange.makeScheduledId();577    const waitForBlocks = 4;578579    await token.scheduleAfter(scheduledId, waitForBlocks)580      .transfer(bob, {Substrate: alice.address});581582    const priority = 112;583    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))584      .to.be.rejectedWith(/BadOrigin/);585586    const priorityChanged = await helper.wait.event(587      waitForBlocks,588      'scheduler',589      'PriorityChanged',590    );591592    expect(priorityChanged === null).to.be.true;593  });594});595596// Implementation of the functionality tested here was postponed/shelved597describe.skip('Sponsoring scheduling', () => {598  // let alice: IKeyringPair;599  // let bob: IKeyringPair;600601  // before(async() => {602  //   await usingApi(async (_, privateKey) => {603  //     alice = privateKey('//Alice');604  //     bob = privateKey('//Bob');605  //   });606  // });607608  it('Can sponsor scheduling a transaction', async () => {609    // const collectionId = await createCollectionExpectSuccess();610    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);611    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');612613    // await usingApi(async api => {614    //   const scheduledId = await makeScheduledId();615    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);616617    //   const bobBalanceBefore = await getFreeBalance(bob);618    //   const waitForBlocks = 4;619    //   // no need to wait to check, fees must be deducted on scheduling, immediately620    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);621    //   const bobBalanceAfter = await getFreeBalance(bob);622    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;623    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;624    //   // wait for sequentiality matters625    //   await waitNewBlocks(waitForBlocks - 1);626    // });627  });628629  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {630    // await usingApi(async (api, privateKey) => {631    //   // Find an empty, unused account632    //   const zeroBalance = await findUnusedAddress(api, privateKey);633634    //   const collectionId = await createCollectionExpectSuccess();635636    //   // Add zeroBalance address to allow list637    //   await enablePublicMintingExpectSuccess(alice, collectionId);638    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);639640    //   // Grace zeroBalance with money, enough to cover future transactions641    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);642    //   await submitTransactionAsync(alice, balanceTx);643644    //   // Mint a fresh NFT645    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');646    //   const scheduledId = await makeScheduledId();647648    //   // Schedule transfer of the NFT a few blocks ahead649    //   const waitForBlocks = 5;650    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);651652    //   // Get rid of the account's funds before the scheduled transaction takes place653    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);654    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);655    //   expect(getGenericResult(events).success).to.be.true;656    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?657    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);658    //   const events = await submitTransactionAsync(alice, sudoTx);659    //   expect(getGenericResult(events).success).to.be.true;*/660661    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions662    //   await waitNewBlocks(waitForBlocks - 3);663664    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));665    // });666  });667668  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {669    // const collectionId = await createCollectionExpectSuccess();670671    // await usingApi(async (api, privateKey) => {672    //   const zeroBalance = await findUnusedAddress(api, privateKey);673    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);674    //   await submitTransactionAsync(alice, balanceTx);675676    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);677    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);678679    //   const scheduledId = await makeScheduledId();680    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);681682    //   const waitForBlocks = 5;683    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);684685    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);686    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);687    //   const events = await submitTransactionAsync(alice, sudoTx);688    //   expect(getGenericResult(events).success).to.be.true;689690    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions691    //   await waitNewBlocks(waitForBlocks - 3);692693    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));694    // });695  });696697  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {698    // const collectionId = await createCollectionExpectSuccess();699    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);700    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');701702    // await usingApi(async (api, privateKey) => {703    //   const zeroBalance = await findUnusedAddress(api, privateKey);704705    //   await enablePublicMintingExpectSuccess(alice, collectionId);706    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);707708    //   const bobBalanceBefore = await getFreeBalance(bob);709710    //   const createData = {nft: {const_data: [], variable_data: []}};711    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);712    //   const scheduledId = await makeScheduledId();713714    //   /*const badTransaction = async function () {715    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);716    //   };717    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/718719    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);720721    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);722    // });723  });724});
after · tests/src/scheduler.seqtest.ts
1// 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 {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {DevUniqueHelper} from './util/playgrounds/unique.dev';2021describe('Scheduling token and balance transfers', () => {22  let superuser: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;25  let charlie: IKeyringPair;2627  before(async function() {28    await usingPlaygrounds(async (helper, privateKey) => {29      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);3031      superuser = await privateKey('//Alice');32      const donor = await privateKey({filename: __filename});33      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);3435      await helper.testUtils.enable();36    });37  });3839  itSub('Can delay a transfer of an owned token', async ({helper}) => {40    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});41    const token = await collection.mintToken(alice);42    const schedulerId = await helper.arrange.makeScheduledId();43    const blocksBeforeExecution = 4;44    45    await token.scheduleAfter(schedulerId, blocksBeforeExecution)46      .transfer(alice, {Substrate: bob.address});47    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;4849    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});5051    await helper.wait.forParachainBlockNumber(executionBlock);5253    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});54  });5556  itSub('Can transfer funds periodically', async ({helper}) => {57    const scheduledId = await helper.arrange.makeScheduledId();58    const waitForBlocks = 1;5960    const amount = 1n * helper.balance.getOneTokenNominal();61    const periodic = {62      period: 2,63      repetitions: 2,64    };6566    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);6768    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})69      .balance.transferToSubstrate(alice, bob.address, amount);70    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;7172    await helper.wait.forParachainBlockNumber(executionBlock);7374    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);75    expect(bobsBalanceAfterFirst)76      .to.be.equal(77        bobsBalanceBefore + 1n * amount,78        '#1 Balance of the recipient should be increased by 1 * amount',79      );8081    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);8283    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);84    expect(bobsBalanceAfterSecond)85      .to.be.equal(86        bobsBalanceBefore + 2n * amount,87        '#2 Balance of the recipient should be increased by 2 * amount',88      );89  });9091  itSub('Can cancel a scheduled operation which has not yet taken effect', async ({helper}) => {92    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});93    const token = await collection.mintToken(alice);9495    const scheduledId = await helper.arrange.makeScheduledId();96    const waitForBlocks = 4;9798    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});99100    await token.scheduleAfter(scheduledId, waitForBlocks)101      .transfer(alice, {Substrate: bob.address});102    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;103104    await helper.scheduler.cancelScheduled(alice, scheduledId);105106    await helper.wait.forParachainBlockNumber(executionBlock);107108    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});109  });110111  itSub('Can cancel a periodic operation (transfer of funds)', async ({helper}) => {112    const waitForBlocks = 1;113    const periodic = {114      period: 3,115      repetitions: 2,116    };117118    const scheduledId = await helper.arrange.makeScheduledId();119120    const amount = 1n * helper.balance.getOneTokenNominal();121122    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);123124    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})125      .balance.transferToSubstrate(alice, bob.address, amount);126    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;127128    await helper.wait.forParachainBlockNumber(executionBlock);129130    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);131132    expect(bobsBalanceAfterFirst)133      .to.be.equal(134        bobsBalanceBefore + 1n * amount,135        '#1 Balance of the recipient should be increased by 1 * amount',136      );137138    await helper.scheduler.cancelScheduled(alice, scheduledId);139    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);140141    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);142    expect(bobsBalanceAfterSecond)143      .to.be.equal(144        bobsBalanceAfterFirst,145        '#2 Balance of the recipient should not be changed',146      );147  });148149  itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.TestUtils], async ({helper}) => {150    const scheduledId = await helper.arrange.makeScheduledId();151    const waitForBlocks = 4;152153    const initTestVal = 42;154    const changedTestVal = 111;155156    await helper.testUtils.setTestValue(alice, initTestVal);157158    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks)159      .testUtils.setTestValueAndRollback(alice, changedTestVal);160    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;161162    await helper.wait.forParachainBlockNumber(executionBlock);163164    const testVal = await helper.testUtils.testValue();165    expect(testVal, 'The test value should NOT be commited')166      .to.be.equal(initTestVal);167  });168169  itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.TestUtils], async function({helper}) {170    const scheduledId = await helper.arrange.makeScheduledId();171    const waitForBlocks = 4;172    const periodic = {173      period: 2,174      repetitions: 2,175    };176177    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);178    const scheduledLen = dummyTx.callIndex.length;179180    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))181      .partialFee.toBigInt();182183    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})184      .testUtils.justTakeFee(alice);185    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;186187    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);188    let diff;189190    await helper.wait.forParachainBlockNumber(executionBlock);191192    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);193    expect(194      aliceBalanceAfterFirst < aliceInitBalance,195      '[after execution #1] Scheduled task should take a fee',196    ).to.be.true;197198    diff = aliceInitBalance - aliceBalanceAfterFirst;199    expect(diff).to.be.equal(200      expectedScheduledFee,201      'Scheduled task should take the right amount of fees',202    );203204    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);205206    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);207    expect(208      aliceBalanceAfterSecond < aliceBalanceAfterFirst,209      '[after execution #2] Scheduled task should take a fee',210    ).to.be.true;211212    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;213    expect(diff).to.be.equal(214      expectedScheduledFee,215      'Scheduled task should take the right amount of fees',216    );217  });218219  // Check if we can cancel a scheduled periodic operation220  // in the same block in which it is running221  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.TestUtils], async ({helper}) => {222    const currentBlockNumber = await helper.chain.getLatestBlockNumber();223    const blocksBeforeExecution = 10;224    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;225226    const [227      scheduledId,228      scheduledCancelId,229    ] = await helper.arrange.makeScheduledIds(2);230231    const periodic = {232      period: 5,233      repetitions: 5,234    };235236    const initTestVal = 0;237    const incTestVal = initTestVal + 1;238    const finalTestVal = initTestVal + 2;239240    await helper.testUtils.setTestValue(alice, initTestVal);241242    await helper.scheduler.scheduleAt<DevUniqueHelper>(scheduledId, firstExecutionBlockNumber, {periodic})243      .testUtils.incTestValue(alice);244245    // Cancel the inc tx after 2 executions246    // *in the same block* in which the second execution is scheduled247    await helper.scheduler.scheduleAt(248      scheduledCancelId,249      firstExecutionBlockNumber + periodic.period,250    ).scheduler.cancelScheduled(alice, scheduledId);251252    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);253254    // execution #0255    expect(await helper.testUtils.testValue())256      .to.be.equal(incTestVal);257258    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);259260    // execution #1261    expect(await helper.testUtils.testValue())262      .to.be.equal(finalTestVal);263264    for (let i = 1; i < periodic.repetitions; i++) {265      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));266      expect(await helper.testUtils.testValue())267        .to.be.equal(finalTestVal);268    }269  });270271  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.TestUtils], async ({helper}) => {272    const scheduledId = await helper.arrange.makeScheduledId();273    const waitForBlocks = 4;274    const periodic = {275      period: 2,276      repetitions: 5,277    };278279    const initTestVal = 0;280    const maxTestVal = 2;281282    await helper.testUtils.setTestValue(alice, initTestVal);283284    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})285      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);286    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;287288    await helper.wait.forParachainBlockNumber(executionBlock);289290    // execution #0291    expect(await helper.testUtils.testValue())292      .to.be.equal(initTestVal + 1);293294    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);295296    // execution #1297    expect(await helper.testUtils.testValue())298      .to.be.equal(initTestVal + 2);299300    await helper.wait.forParachainBlockNumber(executionBlock + 2 * periodic.period);301302    // <canceled>303    expect(await helper.testUtils.testValue())304      .to.be.equal(initTestVal + 2);305  });306307  itSub('Root can cancel any scheduled operation', async ({helper}) => {308    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});309    const token = await collection.mintToken(bob);310311    const scheduledId = await helper.arrange.makeScheduledId();312    const waitForBlocks = 4;313314    await token.scheduleAfter(scheduledId, waitForBlocks)315      .transfer(bob, {Substrate: alice.address});316    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;317318    await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);319320    await helper.wait.forParachainBlockNumber(executionBlock);321322    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});323  });324325  itSub('Root can set prioritized scheduled operation', async ({helper}) => {326    const scheduledId = await helper.arrange.makeScheduledId();327    const waitForBlocks = 4;328329    const amount = 42n * helper.balance.getOneTokenNominal();330331    const balanceBefore = await helper.balance.getSubstrate(charlie.address);332333    await helper.getSudo()334      .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42})335      .balance.forceTransferToSubstrate(superuser, bob.address, charlie.address, amount);336    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;337338    await helper.wait.forParachainBlockNumber(executionBlock);339340    const balanceAfter = await helper.balance.getSubstrate(charlie.address);341342    expect(balanceAfter > balanceBefore).to.be.true;343344    const diff = balanceAfter - balanceBefore;345    expect(diff).to.be.equal(amount);346  });347348  itSub("Root can change scheduled operation's priority", async ({helper}) => {349    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});350    const token = await collection.mintToken(bob);351352    const scheduledId = await helper.arrange.makeScheduledId();353    const waitForBlocks = 6;354355    await token.scheduleAfter(scheduledId, waitForBlocks)356      .transfer(bob, {Substrate: alice.address});357358    const priority = 112;359    await helper.getSudo().scheduler.changePriority(superuser, scheduledId, priority);360361    const priorityChanged = await helper.wait.event(362      waitForBlocks,363      'scheduler',364      'PriorityChanged',365    );366367    expect(priorityChanged !== null).to.be.true;368    expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString());369  });370371  itSub('Prioritized operations execute in valid order', async ({helper}) => {372    const [373      scheduledFirstId,374      scheduledSecondId,375    ] = await helper.arrange.makeScheduledIds(2);376377    const currentBlockNumber = await helper.chain.getLatestBlockNumber();378    const blocksBeforeExecution = 6;379    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;380381    const prioHigh = 0;382    const prioLow = 255;383384    const periodic = {385      period: 6,386      repetitions: 2,387    };388389    const amount = 1n * helper.balance.getOneTokenNominal();390391    // Scheduler a task with a lower priority first, then with a higher priority392    await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic})393      .balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);394395    await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic})396      .balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);397398    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');399400    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);401402    // Flip priorities403    await helper.getSudo().scheduler.changePriority(superuser, scheduledFirstId, prioHigh);404    await helper.getSudo().scheduler.changePriority(superuser, scheduledSecondId, prioLow);405406    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);407408    const dispatchEvents = capture.extractCapturedEvents();409    expect(dispatchEvents.length).to.be.equal(4);410411    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());412413    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];414    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];415416    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);417    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);418419    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);420    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);421  });422423  itSub('Periodic operations always can be rescheduled', async ({helper}) => {424    const maxScheduledPerBlock = 50;425    const numFilledBlocks = 3;426    const ids = await helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);427    const periodicId = ids[0];428    const fillIds = ids.slice(1);429430    const initTestVal = 0;431    const firstExecTestVal = 1;432    const secondExecTestVal = 2;433    await helper.testUtils.setTestValue(alice, initTestVal);434435    const currentBlockNumber = await helper.chain.getLatestBlockNumber();436    const blocksBeforeExecution = 8;437    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;438439    const period = 5;440441    const periodic = {442      period,443      repetitions: 2,444    };445446    // Fill `numFilledBlocks` blocks beginning from the block in which the second execution should occur447    const txs = [];448    for (let offset = 0; offset < numFilledBlocks; offset ++) {449      for (let i = 0; i < maxScheduledPerBlock; i++) {450451        const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);452453        const when = firstExecutionBlockNumber + period + offset;454        const tx = helper.constructApiCall('api.tx.scheduler.scheduleNamed', [fillIds[i + offset * maxScheduledPerBlock], when, null, null, scheduledTx]);455456        txs.push(tx);457      }458    }459    await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);460461    await helper.scheduler.scheduleAt<DevUniqueHelper>(periodicId, firstExecutionBlockNumber, {periodic})462      .testUtils.incTestValue(alice);463464    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);465    expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);466467    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + period + numFilledBlocks);468469    // The periodic operation should be postponed by `numFilledBlocks`470    for (let i = 0; i < numFilledBlocks; i++) {471      expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);472    }473474    // After the `numFilledBlocks` the periodic operation will eventually be executed475    expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);476  });477478  itSub('scheduled operations does not change nonce', async ({helper}) => {479    const scheduledId = await helper.arrange.makeScheduledId();480    const blocksBeforeExecution = 4;481482    await helper.scheduler483      .scheduleAfter<DevUniqueHelper>(scheduledId, blocksBeforeExecution)484      .balance.transferToSubstrate(alice, bob.address, 1n);485    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;486487    const initNonce = await helper.chain.getNonce(alice.address);488489    await helper.wait.forParachainBlockNumber(executionBlock);490491    const finalNonce = await helper.chain.getNonce(alice.address);492493    expect(initNonce).to.be.equal(finalNonce);494  });495});496497describe('Negative Test: Scheduling', () => {498  let alice: IKeyringPair;499  let bob: IKeyringPair;500501  before(async function() {502    await usingPlaygrounds(async (helper, privateKey) => {503      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);504505      const donor = await privateKey({filename: __filename});506      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);507508      await helper.testUtils.enable();509    });510  });511512  itSub("Can't overwrite a scheduled ID", async ({helper}) => {513    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});514    const token = await collection.mintToken(alice);515516    const scheduledId = await helper.arrange.makeScheduledId();517    const waitForBlocks = 4;518519    await token.scheduleAfter(scheduledId, waitForBlocks)520      .transfer(alice, {Substrate: bob.address});521    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;522523    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);524    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))525      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);526527    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);528529    await helper.wait.forParachainBlockNumber(executionBlock);530531    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);532533    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});534    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);535  });536537  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {538    const scheduledId = await helper.arrange.makeScheduledId();539    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))540      .to.be.rejectedWith(/scheduler\.NotFound/);541  });542543  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {544    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});545    const token = await collection.mintToken(alice);546547    const scheduledId = await helper.arrange.makeScheduledId();548    const waitForBlocks = 4;549550    await token.scheduleAfter(scheduledId, waitForBlocks)551      .transfer(alice, {Substrate: bob.address});552    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;553554    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))555      .to.be.rejectedWith(/BadOrigin/);556557    await helper.wait.forParachainBlockNumber(executionBlock);558559    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});560  });561562  itSub("Regular user can't set prioritized scheduled operation", async ({helper}) => {563    const scheduledId = await helper.arrange.makeScheduledId();564    const waitForBlocks = 4;565566    const amount = 42n * helper.balance.getOneTokenNominal();567568    const balanceBefore = await helper.balance.getSubstrate(bob.address);569570    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});571    572    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))573      .to.be.rejectedWith(/BadOrigin/);574575    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;576577    await helper.wait.forParachainBlockNumber(executionBlock);578579    const balanceAfter = await helper.balance.getSubstrate(bob.address);580581    expect(balanceAfter).to.be.equal(balanceBefore);582  });583584  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {585    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});586    const token = await collection.mintToken(bob);587588    const scheduledId = await helper.arrange.makeScheduledId();589    const waitForBlocks = 4;590591    await token.scheduleAfter(scheduledId, waitForBlocks)592      .transfer(bob, {Substrate: alice.address});593594    const priority = 112;595    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))596      .to.be.rejectedWith(/BadOrigin/);597598    const priorityChanged = await helper.wait.event(599      waitForBlocks,600      'scheduler',601      'PriorityChanged',602    );603604    expect(priorityChanged === null).to.be.true;605  });606});607608// Implementation of the functionality tested here was postponed/shelved609describe.skip('Sponsoring scheduling', () => {610  // let alice: IKeyringPair;611  // let bob: IKeyringPair;612613  // before(async() => {614  //   await usingApi(async (_, privateKey) => {615  //     alice = privateKey('//Alice');616  //     bob = privateKey('//Bob');617  //   });618  // });619620  it('Can sponsor scheduling a transaction', async () => {621    // const collectionId = await createCollectionExpectSuccess();622    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);623    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');624625    // await usingApi(async api => {626    //   const scheduledId = await makeScheduledId();627    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);628629    //   const bobBalanceBefore = await getFreeBalance(bob);630    //   const waitForBlocks = 4;631    //   // no need to wait to check, fees must be deducted on scheduling, immediately632    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);633    //   const bobBalanceAfter = await getFreeBalance(bob);634    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;635    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;636    //   // wait for sequentiality matters637    //   await waitNewBlocks(waitForBlocks - 1);638    // });639  });640641  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {642    // await usingApi(async (api, privateKey) => {643    //   // Find an empty, unused account644    //   const zeroBalance = await findUnusedAddress(api, privateKey);645646    //   const collectionId = await createCollectionExpectSuccess();647648    //   // Add zeroBalance address to allow list649    //   await enablePublicMintingExpectSuccess(alice, collectionId);650    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);651652    //   // Grace zeroBalance with money, enough to cover future transactions653    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);654    //   await submitTransactionAsync(alice, balanceTx);655656    //   // Mint a fresh NFT657    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');658    //   const scheduledId = await makeScheduledId();659660    //   // Schedule transfer of the NFT a few blocks ahead661    //   const waitForBlocks = 5;662    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);663664    //   // Get rid of the account's funds before the scheduled transaction takes place665    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);666    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);667    //   expect(getGenericResult(events).success).to.be.true;668    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?669    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);670    //   const events = await submitTransactionAsync(alice, sudoTx);671    //   expect(getGenericResult(events).success).to.be.true;*/672673    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions674    //   await waitNewBlocks(waitForBlocks - 3);675676    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));677    // });678  });679680  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {681    // const collectionId = await createCollectionExpectSuccess();682683    // await usingApi(async (api, privateKey) => {684    //   const zeroBalance = await findUnusedAddress(api, privateKey);685    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);686    //   await submitTransactionAsync(alice, balanceTx);687688    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);689    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);690691    //   const scheduledId = await makeScheduledId();692    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);693694    //   const waitForBlocks = 5;695    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);696697    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);698    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);699    //   const events = await submitTransactionAsync(alice, sudoTx);700    //   expect(getGenericResult(events).success).to.be.true;701702    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions703    //   await waitNewBlocks(waitForBlocks - 3);704705    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));706    // });707  });708709  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {710    // const collectionId = await createCollectionExpectSuccess();711    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);712    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');713714    // await usingApi(async (api, privateKey) => {715    //   const zeroBalance = await findUnusedAddress(api, privateKey);716717    //   await enablePublicMintingExpectSuccess(alice, collectionId);718    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);719720    //   const bobBalanceBefore = await getFreeBalance(bob);721722    //   const createData = {nft: {const_data: [], variable_data: []}};723    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);724    //   const scheduledId = await makeScheduledId();725726    //   /*const badTransaction = async function () {727    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);728    //   };729    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/730731    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);732733    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);734    // });735  });736});