git.delta.rocks / unique-network / refs/commits / cafc9078ea8d

difftreelog

source

js-packages/tests/scheduler.seqtest.ts32.2 KiBsourcehistory
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, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js';18import type {IKeyringPair} from '@polkadot/types/types';19import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';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.UniqueScheduler]);3031      superuser = await privateKey('//Alice');32      const donor = await privateKey({url: import.meta.url});33      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);3435      await helper.testUtils.enable(Pallets.TestUtils);36    });37  });3839  beforeEach(async () => {40    await usingPlaygrounds(async (helper) => {41      await helper.wait.noScheduledTasks();42    });43  });4445  itSched('Can delay a transfer of an owned token', async (scheduleKind, {helper}) => {46    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});47    const token = await collection.mintToken(alice);48    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;49    const blocksBeforeExecution = 4;50    await helper.scheduler.scheduleAfter(blocksBeforeExecution, {scheduledId})51      .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});52    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;5354    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});5556    await helper.wait.forParachainBlockNumber(executionBlock);5758    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});59  });6061  itSched('Can transfer funds periodically', async (scheduleKind, {helper}) => {62    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;63    const waitForBlocks = 1;6465    const amount = 1n * helper.balance.getOneTokenNominal();66    const periodic = {67      period: 2,68      repetitions: 2,69    };7071    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);7273    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, periodic})74      .balance.transferToSubstrate(alice, bob.address, amount);75    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;7677    await helper.wait.forParachainBlockNumber(executionBlock);7879    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);80    expect(bobsBalanceAfterFirst)81      .to.be.equal(82        bobsBalanceBefore + 1n * amount,83        '#1 Balance of the recipient should be increased by 1 * amount',84      );8586    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);8788    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);89    expect(bobsBalanceAfterSecond)90      .to.be.equal(91        bobsBalanceBefore + 2n * amount,92        '#2 Balance of the recipient should be increased by 2 * amount',93      );94  });9596  itSub('Can cancel a scheduled operation which has not yet taken effect', async ({helper}) => {97    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});98    const token = await collection.mintToken(alice);99100    const scheduledId = helper.arrange.makeScheduledId();101    const waitForBlocks = 4;102103    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});104105    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})106      .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});107    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;108109    await helper.scheduler.cancelScheduled(alice, scheduledId);110111    await helper.wait.forParachainBlockNumber(executionBlock);112113    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});114  });115116  itSub('Can cancel a periodic operation (transfer of funds)', async ({helper}) => {117    const waitForBlocks = 1;118    const periodic = {119      period: 3,120      repetitions: 2,121    };122123    const scheduledId = helper.arrange.makeScheduledId();124125    const amount = 1n * helper.balance.getOneTokenNominal();126127    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);128129    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, periodic})130      .balance.transferToSubstrate(alice, bob.address, amount);131    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;132133    await helper.wait.forParachainBlockNumber(executionBlock);134135    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);136137    expect(bobsBalanceAfterFirst)138      .to.be.equal(139        bobsBalanceBefore + 1n * amount,140        '#1 Balance of the recipient should be increased by 1 * amount',141      );142143    await helper.scheduler.cancelScheduled(alice, scheduledId);144    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);145146    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);147    expect(bobsBalanceAfterSecond)148      .to.be.equal(149        bobsBalanceAfterFirst,150        '#2 Balance of the recipient should not be changed',151      );152  });153154  itSched('scheduler will not insert more tasks than allowed', async (scheduleKind, {helper}) => {155    const maxScheduledPerBlock = 50;156    let fillScheduledIds = new Array(maxScheduledPerBlock);157    let extraScheduledId = undefined;158159    if(scheduleKind == 'named') {160      const scheduledIds = helper.arrange.makeScheduledIds(maxScheduledPerBlock + 1);161      fillScheduledIds = scheduledIds.slice(0, maxScheduledPerBlock);162      extraScheduledId = scheduledIds[maxScheduledPerBlock];163    }164165    // Since the dev node has Instant Seal,166    // we need a larger gap between the current block and the target one.167    //168    // We will schedule `maxScheduledPerBlock` transaction into the target block,169    // so we need at least `maxScheduledPerBlock`-wide gap.170    // We add some additional blocks to this gap to mitigate possible PolkadotJS delays.171    const waitForBlocks = await helper.arrange.isDevNode() ? maxScheduledPerBlock + 5 : 5;172173    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks;174175    const amount = 1n * helper.balance.getOneTokenNominal();176177    const balanceBefore = await helper.balance.getSubstrate(bob.address);178179    // Fill the target block180    for(let i = 0; i < maxScheduledPerBlock; i++) {181      await helper.scheduler.scheduleAt(executionBlock, {scheduledId: fillScheduledIds[i]})182        .balance.transferToSubstrate(superuser, bob.address, amount);183    }184185    // Try to schedule a task into a full block186    await expect(helper.scheduler.scheduleAt(executionBlock, {scheduledId: extraScheduledId})187      .balance.transferToSubstrate(superuser, bob.address, amount))188      .to.be.rejectedWith(/scheduler\.AgendaIsExhausted/);189190    await helper.wait.forParachainBlockNumber(executionBlock);191192    const balanceAfter = await helper.balance.getSubstrate(bob.address);193194    expect(balanceAfter > balanceBefore).to.be.true;195196    const diff = balanceAfter - balanceBefore;197    expect(diff).to.be.equal(amount * BigInt(maxScheduledPerBlock));198  });199200  itSched.ifWithPallets('Scheduled tasks are transactional', [Pallets.TestUtils], async (scheduleKind, {helper}) => {201    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;202    const waitForBlocks = 4;203204    const initTestVal = 42;205    const changedTestVal = 111;206207    await helper.testUtils.setTestValue(alice, initTestVal);208209    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId})210      .testUtils.setTestValueAndRollback(alice, changedTestVal);211    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;212213    await helper.wait.forParachainBlockNumber(executionBlock);214215    const testVal = await helper.testUtils.testValue();216    expect(testVal, 'The test value should NOT be commited')217      .to.be.equal(initTestVal);218  });219220  itSched.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.TestUtils], async function(scheduleKind, {helper}) {221    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;222    const waitForBlocks = 4;223    const periodic = {224      period: 2,225      repetitions: 2,226    };227228    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);229    const scheduledLen = dummyTx.callIndex.length;230231    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))232      .partialFee.toBigInt();233234    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId, periodic})235      .testUtils.justTakeFee(alice);236    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;237238    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);239    let diff;240241    await helper.wait.forParachainBlockNumber(executionBlock);242243    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);244    expect(245      aliceBalanceAfterFirst < aliceInitBalance,246      '[after execution #1] Scheduled task should take a fee',247    ).to.be.true;248249    diff = aliceInitBalance - aliceBalanceAfterFirst;250    expect(diff).to.be.equal(251      expectedScheduledFee,252      'Scheduled task should take the right amount of fees',253    );254255    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);256257    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);258    expect(259      aliceBalanceAfterSecond < aliceBalanceAfterFirst,260      '[after execution #2] Scheduled task should take a fee',261    ).to.be.true;262263    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;264    expect(diff).to.be.equal(265      expectedScheduledFee,266      'Scheduled task should take the right amount of fees',267    );268  });269270  // Check if we can cancel a scheduled periodic operation271  // in the same block in which it is running272  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.TestUtils], async ({helper}) => {273    const currentBlockNumber = await helper.chain.getLatestBlockNumber();274    const blocksBeforeExecution = 10;275    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;276277    const [278      scheduledId,279      scheduledCancelId,280    ] = helper.arrange.makeScheduledIds(2);281282    const periodic = {283      period: 5,284      repetitions: 5,285    };286287    const initTestVal = 0;288    const incTestVal = initTestVal + 1;289    const finalTestVal = initTestVal + 2;290291    await helper.testUtils.setTestValue(alice, initTestVal);292293    await helper.scheduler.scheduleAt<DevUniqueHelper>(firstExecutionBlockNumber, {scheduledId, periodic})294      .testUtils.incTestValue(alice);295296    // Cancel the inc tx after 2 executions297    // *in the same block* in which the second execution is scheduled298    await helper.scheduler.scheduleAt(299      firstExecutionBlockNumber + periodic.period,300      {scheduledId: scheduledCancelId},301    ).scheduler.cancelScheduled(alice, scheduledId);302303    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);304305    // execution #0306    expect(await helper.testUtils.testValue())307      .to.be.equal(incTestVal);308309    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);310311    // execution #1312    expect(await helper.testUtils.testValue())313      .to.be.equal(finalTestVal);314315    for(let i = 1; i < periodic.repetitions; i++) {316      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));317      expect(await helper.testUtils.testValue())318        .to.be.equal(finalTestVal);319    }320  });321322  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.TestUtils], async ({helper}) => {323    const scheduledId = helper.arrange.makeScheduledId();324    const waitForBlocks = 4;325    const periodic = {326      period: 2,327      repetitions: 5,328    };329330    const initTestVal = 0;331    const maxTestVal = 2;332333    await helper.testUtils.setTestValue(alice, initTestVal);334335    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId, periodic})336      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);337    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;338339    await helper.wait.forParachainBlockNumber(executionBlock);340341    // execution #0342    expect(await helper.testUtils.testValue())343      .to.be.equal(initTestVal + 1);344345    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);346347    // execution #1348    expect(await helper.testUtils.testValue())349      .to.be.equal(initTestVal + 2);350351    await helper.wait.forParachainBlockNumber(executionBlock + 2 * periodic.period);352353    // <canceled>354    expect(await helper.testUtils.testValue())355      .to.be.equal(initTestVal + 2);356  });357358  itSub('Root can cancel any scheduled operation', async ({helper}) => {359    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});360    const token = await collection.mintToken(bob);361362    const scheduledId = helper.arrange.makeScheduledId();363    const waitForBlocks = 4;364365    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})366      .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});367    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;368369    await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);370371    await helper.wait.forParachainBlockNumber(executionBlock);372373    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});374  });375376  itSched('Root can set prioritized scheduled operation', async (scheduleKind, {helper}) => {377    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;378    const waitForBlocks = 4;379380    const amount = 42n * helper.balance.getOneTokenNominal();381382    const balanceBefore = await helper.balance.getSubstrate(charlie.address);383384    await helper.getSudo()385      .scheduler.scheduleAfter(waitForBlocks, {scheduledId, priority: 42})386      .balance.forceTransferToSubstrate(superuser, bob.address, charlie.address, amount);387    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;388389    await helper.wait.forParachainBlockNumber(executionBlock);390391    const balanceAfter = await helper.balance.getSubstrate(charlie.address);392393    expect(balanceAfter > balanceBefore).to.be.true;394395    const diff = balanceAfter - balanceBefore;396    expect(diff).to.be.equal(amount);397  });398399  itSub("Root can change scheduled operation's priority", async ({helper}) => {400    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});401    const token = await collection.mintToken(bob);402403    const scheduledId = helper.arrange.makeScheduledId();404    const waitForBlocks = 6;405406    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})407      .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});408    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;409410    const priority = 112;411    await helper.getSudo().scheduler.changePriority(superuser, scheduledId, priority);412413    const priorityChanged = await helper.wait.expectEvent(waitForBlocks, Event.UniqueScheduler.PriorityChanged);414415    const [blockNumber, index] = priorityChanged.task();416    expect(blockNumber).to.be.equal(executionBlock);417    expect(index).to.be.equal(0);418419    expect(priorityChanged.priority().toString()).to.be.equal(priority.toString());420  });421422  itSub('Prioritized operations execute in valid order', async ({helper}) => {423    const [424      scheduledFirstId,425      scheduledSecondId,426    ] = helper.arrange.makeScheduledIds(2);427428    const currentBlockNumber = await helper.chain.getLatestBlockNumber();429    const blocksBeforeExecution = 6;430    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;431432    const prioHigh = 0;433    const prioLow = 255;434435    const periodic = {436      period: 6,437      repetitions: 2,438    };439440    const amount = 1n * helper.balance.getOneTokenNominal();441442    // Scheduler a task with a lower priority first, then with a higher priority443    await helper.getSudo().scheduler.scheduleAt(firstExecutionBlockNumber, {444      scheduledId: scheduledFirstId,445      priority: prioLow,446      periodic,447    }).balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);448449    await helper.getSudo().scheduler.scheduleAt(firstExecutionBlockNumber, {450      scheduledId: scheduledSecondId,451      priority: prioHigh,452      periodic,453    }).balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);454455    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');456457    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);458459    // Flip priorities460    await helper.getSudo().scheduler.changePriority(superuser, scheduledFirstId, prioHigh);461    await helper.getSudo().scheduler.changePriority(superuser, scheduledSecondId, prioLow);462463    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);464465    const dispatchEvents = capture.extractCapturedEvents();466    expect(dispatchEvents.length).to.be.equal(4);467468    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());469470    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];471    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];472473    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);474    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);475476    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);477    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);478  });479480  itSched('Periodic operations always can be rescheduled', async (scheduleKind, {helper}) => {481    const maxScheduledPerBlock = 50;482    const numFilledBlocks = 3;483    const ids = helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);484    const periodicId = scheduleKind == 'named' ? ids[0] : undefined;485    const fillIds = ids.slice(1);486487    const fillScheduleFn = scheduleKind == 'named' ? 'scheduleNamed' : 'schedule';488489    const initTestVal = 0;490    const firstExecTestVal = 1;491    const secondExecTestVal = 2;492    await helper.testUtils.setTestValue(alice, initTestVal);493494    const currentBlockNumber = await helper.chain.getLatestBlockNumber();495    const blocksBeforeExecution = 8;496    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;497498    const period = 5;499500    const periodic = {501      period,502      repetitions: 2,503    };504505    // Fill `numFilledBlocks` blocks beginning from the block in which the second execution should occur506    const txs = [];507    for(let offset = 0; offset < numFilledBlocks; offset ++) {508      for(let i = 0; i < maxScheduledPerBlock; i++) {509510        const scheduledTx = helper.constructApiCall('api.tx.balances.transferKeepAlive', [bob.address, 1n]);511512        const when = firstExecutionBlockNumber + period + offset;513        const mandatoryArgs = [when, null, null, scheduledTx];514        const scheduleArgs = scheduleKind == 'named'515          ? [fillIds[i + offset * maxScheduledPerBlock], ...mandatoryArgs]516          : mandatoryArgs;517518        const tx = helper.constructApiCall(`api.tx.scheduler.${fillScheduleFn}`, scheduleArgs);519520        txs.push(tx);521      }522    }523    await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);524525    await helper.scheduler.scheduleAt<DevUniqueHelper>(firstExecutionBlockNumber, {526      scheduledId: periodicId,527      periodic,528    }).testUtils.incTestValue(alice);529530    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);531    expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);532533    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + period + numFilledBlocks);534535    // The periodic operation should be postponed by `numFilledBlocks`536    for(let i = 0; i < numFilledBlocks; i++) {537      expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);538    }539540    // After the `numFilledBlocks` the periodic operation will eventually be executed541    expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);542  });543544  itSched('scheduled operations does not change nonce', async (scheduleKind, {helper}) => {545    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;546    const blocksBeforeExecution = 4;547548    await helper.scheduler549      .scheduleAfter<DevUniqueHelper>(blocksBeforeExecution, {scheduledId})550      .balance.transferToSubstrate(alice, bob.address, 1n);551    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;552553    const initNonce = await helper.chain.getNonce(alice.address);554555    await helper.wait.forParachainBlockNumber(executionBlock);556557    const finalNonce = await helper.chain.getNonce(alice.address);558559    expect(initNonce).to.be.equal(finalNonce);560  });561});562563describe('Negative Test: Scheduling', () => {564  let alice: IKeyringPair;565  let bob: IKeyringPair;566567  before(async function() {568    await usingPlaygrounds(async (helper, privateKey) => {569      requirePalletsOrSkip(this, helper, [Pallets.UniqueScheduler]);570571      const donor = await privateKey({url: import.meta.url});572      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);573574      await helper.testUtils.enable(Pallets.TestUtils);575    });576  });577578  itSub("Can't overwrite a scheduled ID", async ({helper}) => {579    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});580    const token = await collection.mintToken(alice);581582    const scheduledId = helper.arrange.makeScheduledId();583    const waitForBlocks = 4;584585    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})586      .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});587    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;588589    const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});590    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))591      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);592593    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);594595    await helper.wait.forParachainBlockNumber(executionBlock);596597    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);598599    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});600    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);601  });602603  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {604    const scheduledId = helper.arrange.makeScheduledId();605    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))606      .to.be.rejectedWith(/scheduler\.NotFound/);607  });608609  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {610    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});611    const token = await collection.mintToken(alice);612613    const scheduledId = helper.arrange.makeScheduledId();614    const waitForBlocks = 4;615616    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})617      .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});618    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;619620    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))621      .to.be.rejectedWith(/BadOrigin/);622623    await helper.wait.forParachainBlockNumber(executionBlock);624625    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});626  });627628  itSched("Regular user can't set prioritized scheduled operation", async (scheduleKind, {helper}) => {629    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;630    const waitForBlocks = 4;631632    const amount = 42n * helper.balance.getOneTokenNominal();633634    const balanceBefore = await helper.balance.getSubstrate(bob.address);635636    const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, priority: 42});637638    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))639      .to.be.rejectedWith(/BadOrigin/);640641    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;642643    await helper.wait.forParachainBlockNumber(executionBlock);644645    const balanceAfter = await helper.balance.getSubstrate(bob.address);646647    expect(balanceAfter).to.be.equal(balanceBefore);648  });649650  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {651    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});652    const token = await collection.mintToken(bob);653654    const scheduledId = helper.arrange.makeScheduledId();655    const waitForBlocks = 4;656657    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})658      .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});659660    const priority = 112;661    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))662      .to.be.rejectedWith(/BadOrigin/);663664    await helper.wait.expectEvent(waitForBlocks, Event.UniqueScheduler.PriorityChanged);665  });666});667668// Implementation of the functionality tested here was postponed/shelved669describe.skip('Sponsoring scheduling', () => {670  // let alice: IKeyringPair;671  // let bob: IKeyringPair;672673  // before(async() => {674  //   await usingApi(async (_, privateKey) => {675  //     alice = privateKey('//Alice');676  //     bob = privateKey('//Bob');677  //   });678  // });679680  it('Can sponsor scheduling a transaction', async () => {681    // const collectionId = await createCollectionExpectSuccess();682    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);683    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');684685    // await usingApi(async api => {686    //   const scheduledId = await makeScheduledId();687    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);688689    //   const bobBalanceBefore = await getFreeBalance(bob);690    //   const waitForBlocks = 4;691    //   // no need to wait to check, fees must be deducted on scheduling, immediately692    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);693    //   const bobBalanceAfter = await getFreeBalance(bob);694    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;695    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;696    //   // wait for sequentiality matters697    //   await waitNewBlocks(waitForBlocks - 1);698    // });699  });700701  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {702    // await usingApi(async (api, privateKey) => {703    //   // Find an empty, unused account704    //   const zeroBalance = await findUnusedAddress(api, privateKey);705706    //   const collectionId = await createCollectionExpectSuccess();707708    //   // Add zeroBalance address to allow list709    //   await enablePublicMintingExpectSuccess(alice, collectionId);710    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);711712    //   // Grace zeroBalance with money, enough to cover future transactions713    //   const balanceTx = api.tx.balances.transferKeepAlive(zeroBalance.address, 1n * UNIQUE);714    //   await submitTransactionAsync(alice, balanceTx);715716    //   // Mint a fresh NFT717    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');718    //   const scheduledId = await makeScheduledId();719720    //   // Schedule transfer of the NFT a few blocks ahead721    //   const waitForBlocks = 5;722    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);723724    //   // Get rid of the account's funds before the scheduled transaction takes place725    //   const balanceTx2 = api.tx.balances.transferKeepAlive(alice.address, UNIQUE * 68n / 100n);726    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);727    //   expect(getGenericResult(events).success).to.be.true;728    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?729    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);730    //   const events = await submitTransactionAsync(alice, sudoTx);731    //   expect(getGenericResult(events).success).to.be.true;*/732733    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions734    //   await waitNewBlocks(waitForBlocks - 3);735736    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));737    // });738  });739740  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {741    // const collectionId = await createCollectionExpectSuccess();742743    // await usingApi(async (api, privateKey) => {744    //   const zeroBalance = await findUnusedAddress(api, privateKey);745    //   const balanceTx = api.tx.balances.transferKeepAlive(zeroBalance.address, 1n * UNIQUE);746    //   await submitTransactionAsync(alice, balanceTx);747748    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);749    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);750751    //   const scheduledId = await makeScheduledId();752    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);753754    //   const waitForBlocks = 5;755    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);756757    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);758    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);759    //   const events = await submitTransactionAsync(alice, sudoTx);760    //   expect(getGenericResult(events).success).to.be.true;761762    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions763    //   await waitNewBlocks(waitForBlocks - 3);764765    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));766    // });767  });768769  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {770    // const collectionId = await createCollectionExpectSuccess();771    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);772    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');773774    // await usingApi(async (api, privateKey) => {775    //   const zeroBalance = await findUnusedAddress(api, privateKey);776777    //   await enablePublicMintingExpectSuccess(alice, collectionId);778    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);779780    //   const bobBalanceBefore = await getFreeBalance(bob);781782    //   const createData = {nft: {const_data: [], variable_data: []}};783    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);784    //   const scheduledId = await makeScheduledId();785786    //   /*const badTransaction = async function () {787    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);788    //   };789    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/790791    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);792793    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);794    // });795  });796});