git.delta.rocks / unique-network / refs/commits / 26023a4f157f

difftreelog

tests(scheduler): exclude from parallel execution + adapt accounts

Fahrrader2022-11-01parent: #bd896f3.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 alice: IKeyringPair;23  let bob: IKeyringPair;24  let charlie: IKeyringPair;2526  before(async function() {27    await usingPlaygrounds(async (helper, privateKeyWrapper) => {28      alice = await privateKeyWrapper('//Alice');29      bob = await privateKeyWrapper('//Bob');30      charlie = await privateKeyWrapper('//Charlie');3132      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);3334      await helper.testUtils.enable();35    });36  });3738  itSub('Can delay a transfer of an owned token', async ({helper}) => {39    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});40    const token = await collection.mintToken(alice);41    const schedulerId = await helper.arrange.makeScheduledId();42    const blocksBeforeExecution = 4;4344    await token.scheduleAfter(schedulerId, blocksBeforeExecution)45      .transfer(alice, {Substrate: bob.address});4647    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});4849    await helper.wait.newBlocks(blocksBeforeExecution + 1);5051    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});52  });5354  itSub('Can transfer funds periodically', async ({helper}) => {55    const scheduledId = await helper.arrange.makeScheduledId();56    const waitForBlocks = 1;5758    const amount = 1n * helper.balance.getOneTokenNominal();59    const periodic = {60      period: 2,61      repetitions: 2,62    };6364    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);6566    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})67      .balance.transferToSubstrate(alice, bob.address, amount);6869    await helper.wait.newBlocks(waitForBlocks + 1);7071    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);72    expect(bobsBalanceAfterFirst)73      .to.be.equal(74        bobsBalanceBefore + 1n * amount,75        '#1 Balance of the recipient should be increased by 1 * amount',76      );7778    await helper.wait.newBlocks(periodic.period);7980    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);81    expect(bobsBalanceAfterSecond)82      .to.be.equal(83        bobsBalanceBefore + 2n * amount,84        '#2 Balance of the recipient should be increased by 2 * amount',85      );86  });8788  itSub('Can cancel a scheduled operation which has not yet taken effect', async ({helper}) => {89    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});90    const token = await collection.mintToken(alice);9192    const scheduledId = await helper.arrange.makeScheduledId();93    const waitForBlocks = 4;9495    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});9697    await token.scheduleAfter(scheduledId, waitForBlocks)98      .transfer(alice, {Substrate: bob.address});99100    await helper.scheduler.cancelScheduled(alice, scheduledId);101102    await helper.wait.newBlocks(waitForBlocks + 1);103104    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});105  });106107  itSub('Can cancel a periodic operation (transfer of funds)', async ({helper}) => {108    const waitForBlocks = 1;109    const periodic = {110      period: 3,111      repetitions: 2,112    };113114    const scheduledId = await helper.arrange.makeScheduledId();115116    const amount = 1n * helper.balance.getOneTokenNominal();117118    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);119120    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})121      .balance.transferToSubstrate(alice, bob.address, amount);122123    await helper.wait.newBlocks(waitForBlocks + 1);124125    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);126127    expect(bobsBalanceAfterFirst)128      .to.be.equal(129        bobsBalanceBefore + 1n * amount,130        '#1 Balance of the recipient should be increased by 1 * amount',131      );132133    await helper.scheduler.cancelScheduled(alice, scheduledId);134    await helper.wait.newBlocks(periodic.period);135136    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);137    expect(bobsBalanceAfterSecond)138      .to.be.equal(139        bobsBalanceAfterFirst,140        '#2 Balance of the recipient should not be changed',141      );142  });143144  itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.TestUtils], async ({helper}) => {145    const scheduledId = await helper.arrange.makeScheduledId();146    const waitForBlocks = 4;147148    const initTestVal = 42;149    const changedTestVal = 111;150151    await helper.testUtils.setTestValue(alice, initTestVal);152153    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks)154      .testUtils.setTestValueAndRollback(alice, changedTestVal);155156    await helper.wait.newBlocks(waitForBlocks + 1);157158    const testVal = await helper.testUtils.testValue();159    expect(testVal, 'The test value should NOT be commited')160      .to.be.equal(initTestVal);161  });162163  itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.TestUtils], async function({helper}) {164    const scheduledId = await helper.arrange.makeScheduledId();165    const waitForBlocks = 4;166    const periodic = {167      period: 2,168      repetitions: 2,169    };170171    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);172    const scheduledLen = dummyTx.callIndex.length;173174    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))175      .partialFee.toBigInt();176177    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})178      .testUtils.justTakeFee(alice);179180    await helper.wait.newBlocks(1);181182    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);183    let diff;184185    await helper.wait.newBlocks(waitForBlocks);186187    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);188    expect(189      aliceBalanceAfterFirst < aliceInitBalance,190      '[after execution #1] Scheduled task should take a fee',191    ).to.be.true;192193    diff = aliceInitBalance - aliceBalanceAfterFirst;194    expect(diff).to.be.equal(195      expectedScheduledFee,196      'Scheduled task should take the right amount of fees',197    );198199    await helper.wait.newBlocks(periodic.period);200201    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);202    expect(203      aliceBalanceAfterSecond < aliceBalanceAfterFirst,204      '[after execution #2] Scheduled task should take a fee',205    ).to.be.true;206207    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;208    expect(diff).to.be.equal(209      expectedScheduledFee,210      'Scheduled task should take the right amount of fees',211    );212  });213214  // Check if we can cancel a scheduled periodic operation215  // in the same block in which it is running216  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.TestUtils], async ({helper}) => {217    const currentBlockNumber = await helper.chain.getLatestBlockNumber();218    const blocksBeforeExecution = 10;219    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;220221    const [222      scheduledId,223      scheduledCancelId,224    ] = await helper.arrange.makeScheduledIds(2);225226    const periodic = {227      period: 5,228      repetitions: 5,229    };230231    const initTestVal = 0;232    const incTestVal = initTestVal + 1;233    const finalTestVal = initTestVal + 2;234235    await helper.testUtils.setTestValue(alice, initTestVal);236237    await helper.scheduler.scheduleAt<DevUniqueHelper>(scheduledId, firstExecutionBlockNumber, {periodic})238      .testUtils.incTestValue(alice);239240    // Cancel the inc tx after 2 executions241    // *in the same block* in which the second execution is scheduled242    await helper.scheduler.scheduleAt(243      scheduledCancelId,244      firstExecutionBlockNumber + periodic.period,245    ).scheduler.cancelScheduled(alice, scheduledId);246247    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);248249    // execution #0250    expect(await helper.testUtils.testValue())251      .to.be.equal(incTestVal);252253    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);254255    // execution #1256    expect(await helper.testUtils.testValue())257      .to.be.equal(finalTestVal);258259    for (let i = 1; i < periodic.repetitions; i++) {260      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));261      expect(await helper.testUtils.testValue())262        .to.be.equal(finalTestVal);263    }264  });265266  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.TestUtils], async ({helper}) => {267    const scheduledId = await helper.arrange.makeScheduledId();268    const waitForBlocks = 4;269    const periodic = {270      period: 2,271      repetitions: 5,272    };273274    const initTestVal = 0;275    const maxTestVal = 2;276277    await helper.testUtils.setTestValue(alice, initTestVal);278279    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})280      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);281282    await helper.wait.newBlocks(waitForBlocks + 1);283284    // execution #0285    expect(await helper.testUtils.testValue())286      .to.be.equal(initTestVal + 1);287288    await helper.wait.newBlocks(periodic.period);289290    // execution #1291    expect(await helper.testUtils.testValue())292      .to.be.equal(initTestVal + 2);293294    await helper.wait.newBlocks(periodic.period);295296    // <canceled>297    expect(await helper.testUtils.testValue())298      .to.be.equal(initTestVal + 2);299  });300301  itSub('Root can cancel any scheduled operation', async ({helper}) => {302    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});303    const token = await collection.mintToken(bob);304305    const scheduledId = await helper.arrange.makeScheduledId();306    const waitForBlocks = 4;307308    await token.scheduleAfter(scheduledId, waitForBlocks)309      .transfer(bob, {Substrate: alice.address});310311    await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId);312313    await helper.wait.newBlocks(waitForBlocks + 1);314315    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});316  });317318  itSub('Root can set prioritized scheduled operation', async ({helper}) => {319    const scheduledId = await helper.arrange.makeScheduledId();320    const waitForBlocks = 4;321322    const amount = 42n * helper.balance.getOneTokenNominal();323324    const balanceBefore = await helper.balance.getSubstrate(charlie.address);325326    await helper.getSudo()327      .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42})328      .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount);329330    await helper.wait.newBlocks(waitForBlocks + 1);331332    const balanceAfter = await helper.balance.getSubstrate(charlie.address);333334    expect(balanceAfter > balanceBefore).to.be.true;335336    const diff = balanceAfter - balanceBefore;337    expect(diff).to.be.equal(amount);338  });339340  itSub("Root can change scheduled operation's priority", async ({helper}) => {341    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});342    const token = await collection.mintToken(bob);343344    const scheduledId = await helper.arrange.makeScheduledId();345    const waitForBlocks = 6;346347    await token.scheduleAfter(scheduledId, waitForBlocks)348      .transfer(bob, {Substrate: alice.address});349350    const priority = 112;351    await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority);352353    const priorityChanged = await helper.wait.event(354      waitForBlocks,355      'scheduler',356      'PriorityChanged',357    );358359    expect(priorityChanged !== null).to.be.true;360    expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString());361  });362363  itSub('Prioritized operations executes in valid order', async ({helper}) => {364    const [365      scheduledFirstId,366      scheduledSecondId,367    ] = await helper.arrange.makeScheduledIds(2);368369    const currentBlockNumber = await helper.chain.getLatestBlockNumber();370    const blocksBeforeExecution = 6;371    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;372373    const prioHigh = 0;374    const prioLow = 255;375376    const periodic = {377      period: 6,378      repetitions: 2,379    };380381    const amount = 1n * helper.balance.getOneTokenNominal();382383    // Scheduler a task with a lower priority first, then with a higher priority384    await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic})385      .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);386387    await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic})388      .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);389390    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');391392    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);393394    // Flip priorities395    await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh);396    await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow);397398    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);399400    const dispatchEvents = capture.extractCapturedEvents();401    expect(dispatchEvents.length).to.be.equal(4);402403    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());404405    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];406    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];407408    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);409    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);410411    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);412    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);413  });414415  itSub('Periodic operations always can be rescheduled', async ({helper}) => {416    const maxScheduledPerBlock = 50;417    const numFilledBlocks = 3;418    const ids = await helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);419    const periodicId = ids[0];420    const fillIds = ids.slice(1);421422    const initTestVal = 0;423    const firstExecTestVal = 1;424    const secondExecTestVal = 2;425    await helper.testUtils.setTestValue(alice, initTestVal);426427    const currentBlockNumber = await helper.chain.getLatestBlockNumber();428    const blocksBeforeExecution = 8;429    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;430431    const period = 5;432433    const periodic = {434      period,435      repetitions: 2,436    };437438    // Fill `numFilledBlocks` blocks beginning from the block in which the second execution should occur439    const txs = [];440    for (let offset = 0; offset < numFilledBlocks; offset ++) {441      for (let i = 0; i < maxScheduledPerBlock; i++) {442443        const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);444445        const when = firstExecutionBlockNumber + period + offset;446        const tx = helper.constructApiCall('api.tx.scheduler.scheduleNamed', [fillIds[i + offset * maxScheduledPerBlock], when, null, null, scheduledTx]);447448        txs.push(tx);449      }450    }451    await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);452453    await helper.scheduler.scheduleAt<DevUniqueHelper>(periodicId, firstExecutionBlockNumber, {periodic})454      .testUtils.incTestValue(alice);455456    await helper.wait.newBlocks(blocksBeforeExecution);457    expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);458459    await helper.wait.newBlocks(period + numFilledBlocks);460461    // The periodic operation should be postponed by `numFilledBlocks`462    for (let i = 0; i < numFilledBlocks; i++) {463      expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);464    }465466    // After the `numFilledBlocks` the periodic operation will eventually be executed467    expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);468  });469});470471describe('Negative Test: Scheduling', () => {472  let alice: IKeyringPair;473  let bob: IKeyringPair;474475  before(async function() {476    await usingPlaygrounds(async (helper, privateKeyWrapper) => {477      alice = await privateKeyWrapper('//Alice');478      bob = await privateKeyWrapper('//Bob');479480      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);481482      await helper.testUtils.enable();483    });484  });485486  itSub("Can't overwrite a scheduled ID", async ({helper}) => {487    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});488    const token = await collection.mintToken(alice);489490    const scheduledId = await helper.arrange.makeScheduledId();491    const waitForBlocks = 4;492493    await token.scheduleAfter(scheduledId, waitForBlocks)494      .transfer(alice, {Substrate: bob.address});495496    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);497    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))498      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);499500    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);501502    await helper.wait.newBlocks(waitForBlocks + 1);503504    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);505506    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});507    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);508  });509510  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {511    const scheduledId = await helper.arrange.makeScheduledId();512    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))513      .to.be.rejectedWith(/scheduler\.NotFound/);514  });515516  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {517    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});518    const token = await collection.mintToken(alice);519520    const scheduledId = await helper.arrange.makeScheduledId();521    const waitForBlocks = 4;522523    await token.scheduleAfter(scheduledId, waitForBlocks)524      .transfer(alice, {Substrate: bob.address});525526    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))527      .to.be.rejectedWith(/BadOrigin/);528529    await helper.wait.newBlocks(waitForBlocks + 1);530531    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});532  });533534  itSub("Regular user can't set prioritized scheduled operation", async ({helper}) => {535    const scheduledId = await helper.arrange.makeScheduledId();536    const waitForBlocks = 4;537538    const amount = 42n * helper.balance.getOneTokenNominal();539540    const balanceBefore = await helper.balance.getSubstrate(bob.address);541542    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});543    544    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))545      .to.be.rejectedWith(/BadOrigin/);546547    await helper.wait.newBlocks(waitForBlocks + 1);548549    const balanceAfter = await helper.balance.getSubstrate(bob.address);550551    expect(balanceAfter).to.be.equal(balanceBefore);552  });553554  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {555    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});556    const token = await collection.mintToken(bob);557558    const scheduledId = await helper.arrange.makeScheduledId();559    const waitForBlocks = 4;560561    await token.scheduleAfter(scheduledId, waitForBlocks)562      .transfer(bob, {Substrate: alice.address});563564    const priority = 112;565    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))566      .to.be.rejectedWith(/BadOrigin/);567568    const priorityChanged = await helper.wait.event(569      waitForBlocks,570      'scheduler',571      'PriorityChanged',572    );573574    expect(priorityChanged === null).to.be.true;575  });576});577578// Implementation of the functionality tested here was postponed/shelved579describe.skip('Sponsoring scheduling', () => {580  // let alice: IKeyringPair;581  // let bob: IKeyringPair;582583  // before(async() => {584  //   await usingApi(async (_, privateKeyWrapper) => {585  //     alice = privateKeyWrapper('//Alice');586  //     bob = privateKeyWrapper('//Bob');587  //   });588  // });589590  it('Can sponsor scheduling a transaction', async () => {591    // const collectionId = await createCollectionExpectSuccess();592    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);593    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');594595    // await usingApi(async api => {596    //   const scheduledId = await makeScheduledId();597    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);598599    //   const bobBalanceBefore = await getFreeBalance(bob);600    //   const waitForBlocks = 4;601    //   // no need to wait to check, fees must be deducted on scheduling, immediately602    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);603    //   const bobBalanceAfter = await getFreeBalance(bob);604    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;605    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;606    //   // wait for sequentiality matters607    //   await waitNewBlocks(waitForBlocks - 1);608    // });609  });610611  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {612    // await usingApi(async (api, privateKeyWrapper) => {613    //   // Find an empty, unused account614    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);615616    //   const collectionId = await createCollectionExpectSuccess();617618    //   // Add zeroBalance address to allow list619    //   await enablePublicMintingExpectSuccess(alice, collectionId);620    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);621622    //   // Grace zeroBalance with money, enough to cover future transactions623    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);624    //   await submitTransactionAsync(alice, balanceTx);625626    //   // Mint a fresh NFT627    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');628    //   const scheduledId = await makeScheduledId();629630    //   // Schedule transfer of the NFT a few blocks ahead631    //   const waitForBlocks = 5;632    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);633634    //   // Get rid of the account's funds before the scheduled transaction takes place635    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);636    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);637    //   expect(getGenericResult(events).success).to.be.true;638    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?639    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);640    //   const events = await submitTransactionAsync(alice, sudoTx);641    //   expect(getGenericResult(events).success).to.be.true;*/642643    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions644    //   await waitNewBlocks(waitForBlocks - 3);645646    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));647    // });648  });649650  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {651    // const collectionId = await createCollectionExpectSuccess();652653    // await usingApi(async (api, privateKeyWrapper) => {654    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);655    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);656    //   await submitTransactionAsync(alice, balanceTx);657658    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);659    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);660661    //   const scheduledId = await makeScheduledId();662    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);663664    //   const waitForBlocks = 5;665    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);666667    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);668    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);669    //   const events = await submitTransactionAsync(alice, sudoTx);670    //   expect(getGenericResult(events).success).to.be.true;671672    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions673    //   await waitNewBlocks(waitForBlocks - 3);674675    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));676    // });677  });678679  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {680    // const collectionId = await createCollectionExpectSuccess();681    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);682    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');683684    // await usingApi(async (api, privateKeyWrapper) => {685    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);686687    //   await enablePublicMintingExpectSuccess(alice, collectionId);688    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);689690    //   const bobBalanceBefore = await getFreeBalance(bob);691692    //   const createData = {nft: {const_data: [], variable_data: []}};693    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);694    //   const scheduledId = await makeScheduledId();695696    //   /*const badTransaction = async function () {697    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);698    //   };699    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/700701    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);702703    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);704    // });705  });706});
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;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  });470});471472describe('Negative Test: Scheduling', () => {473  let alice: IKeyringPair;474  let bob: IKeyringPair;475476  before(async function() {477    await usingPlaygrounds(async (helper, privateKey) => {478      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);479480      const donor = await privateKey({filename: __filename});481      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);482483      await helper.testUtils.enable();484    });485  });486487  itSub("Can't overwrite a scheduled ID", async ({helper}) => {488    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});489    const token = await collection.mintToken(alice);490491    const scheduledId = await helper.arrange.makeScheduledId();492    const waitForBlocks = 4;493494    await token.scheduleAfter(scheduledId, waitForBlocks)495      .transfer(alice, {Substrate: bob.address});496497    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);498    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))499      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);500501    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);502503    await helper.wait.newBlocks(waitForBlocks + 1);504505    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);506507    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});508    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);509  });510511  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {512    const scheduledId = await helper.arrange.makeScheduledId();513    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))514      .to.be.rejectedWith(/scheduler\.NotFound/);515  });516517  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {518    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});519    const token = await collection.mintToken(alice);520521    const scheduledId = await helper.arrange.makeScheduledId();522    const waitForBlocks = 4;523524    await token.scheduleAfter(scheduledId, waitForBlocks)525      .transfer(alice, {Substrate: bob.address});526527    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))528      .to.be.rejectedWith(/BadOrigin/);529530    await helper.wait.newBlocks(waitForBlocks + 1);531532    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});533  });534535  itSub("Regular user can't set prioritized scheduled operation", async ({helper}) => {536    const scheduledId = await helper.arrange.makeScheduledId();537    const waitForBlocks = 4;538539    const amount = 42n * helper.balance.getOneTokenNominal();540541    const balanceBefore = await helper.balance.getSubstrate(bob.address);542543    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});544    545    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))546      .to.be.rejectedWith(/BadOrigin/);547548    await helper.wait.newBlocks(waitForBlocks + 1);549550    const balanceAfter = await helper.balance.getSubstrate(bob.address);551552    expect(balanceAfter).to.be.equal(balanceBefore);553  });554555  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {556    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});557    const token = await collection.mintToken(bob);558559    const scheduledId = await helper.arrange.makeScheduledId();560    const waitForBlocks = 4;561562    await token.scheduleAfter(scheduledId, waitForBlocks)563      .transfer(bob, {Substrate: alice.address});564565    const priority = 112;566    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))567      .to.be.rejectedWith(/BadOrigin/);568569    const priorityChanged = await helper.wait.event(570      waitForBlocks,571      'scheduler',572      'PriorityChanged',573    );574575    expect(priorityChanged === null).to.be.true;576  });577});578579// Implementation of the functionality tested here was postponed/shelved580describe.skip('Sponsoring scheduling', () => {581  // let alice: IKeyringPair;582  // let bob: IKeyringPair;583584  // before(async() => {585  //   await usingApi(async (_, privateKey) => {586  //     alice = privateKey('//Alice');587  //     bob = privateKey('//Bob');588  //   });589  // });590591  it('Can sponsor scheduling a transaction', async () => {592    // const collectionId = await createCollectionExpectSuccess();593    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);594    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');595596    // await usingApi(async api => {597    //   const scheduledId = await makeScheduledId();598    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);599600    //   const bobBalanceBefore = await getFreeBalance(bob);601    //   const waitForBlocks = 4;602    //   // no need to wait to check, fees must be deducted on scheduling, immediately603    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);604    //   const bobBalanceAfter = await getFreeBalance(bob);605    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;606    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;607    //   // wait for sequentiality matters608    //   await waitNewBlocks(waitForBlocks - 1);609    // });610  });611612  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {613    // await usingApi(async (api, privateKey) => {614    //   // Find an empty, unused account615    //   const zeroBalance = await findUnusedAddress(api, privateKey);616617    //   const collectionId = await createCollectionExpectSuccess();618619    //   // Add zeroBalance address to allow list620    //   await enablePublicMintingExpectSuccess(alice, collectionId);621    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);622623    //   // Grace zeroBalance with money, enough to cover future transactions624    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);625    //   await submitTransactionAsync(alice, balanceTx);626627    //   // Mint a fresh NFT628    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');629    //   const scheduledId = await makeScheduledId();630631    //   // Schedule transfer of the NFT a few blocks ahead632    //   const waitForBlocks = 5;633    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);634635    //   // Get rid of the account's funds before the scheduled transaction takes place636    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);637    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);638    //   expect(getGenericResult(events).success).to.be.true;639    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?640    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);641    //   const events = await submitTransactionAsync(alice, sudoTx);642    //   expect(getGenericResult(events).success).to.be.true;*/643644    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions645    //   await waitNewBlocks(waitForBlocks - 3);646647    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));648    // });649  });650651  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {652    // const collectionId = await createCollectionExpectSuccess();653654    // await usingApi(async (api, privateKey) => {655    //   const zeroBalance = await findUnusedAddress(api, privateKey);656    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);657    //   await submitTransactionAsync(alice, balanceTx);658659    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);660    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);661662    //   const scheduledId = await makeScheduledId();663    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);664665    //   const waitForBlocks = 5;666    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);667668    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);669    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx 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, save for 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(zeroBalance.address));677    // });678  });679680  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {681    // const collectionId = await createCollectionExpectSuccess();682    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);683    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');684685    // await usingApi(async (api, privateKey) => {686    //   const zeroBalance = await findUnusedAddress(api, privateKey);687688    //   await enablePublicMintingExpectSuccess(alice, collectionId);689    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);690691    //   const bobBalanceBefore = await getFreeBalance(bob);692693    //   const createData = {nft: {const_data: [], variable_data: []}};694    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);695    //   const scheduledId = await makeScheduledId();696697    //   /*const badTransaction = async function () {698    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);699    //   };700    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/701702    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);703704    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);705    // });706  });707});