1234567891011121314151617import {expect, itSub, Pallets, 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 () => {27 await usingPlaygrounds(async (helper, privateKeyWrapper) => {28 alice = await privateKeyWrapper('//Alice');29 bob = await privateKeyWrapper('//Bob');30 charlie = await privateKeyWrapper('//Charlie');3132 await helper.testUtils.enable();33 });34 });3536 itSub.ifWithPallets('Can delay a transfer of an owned token', [Pallets.Scheduler], async ({helper}) => {37 const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});38 const token = await collection.mintToken(alice);39 const schedulerId = await helper.arrange.makeScheduledId();40 const blocksBeforeExecution = 4;4142 await token.scheduleAfter(schedulerId, blocksBeforeExecution)43 .transfer(alice, {Substrate: bob.address});4445 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});4647 await helper.wait.newBlocks(blocksBeforeExecution + 1);4849 expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});50 });5152 itSub.ifWithPallets('Can transfer funds periodically', [Pallets.Scheduler], async ({helper}) => {53 const scheduledId = await helper.arrange.makeScheduledId();54 const waitForBlocks = 1;5556 const amount = 1n * helper.balance.getOneTokenNominal();57 const periodic = {58 period: 2,59 repetitions: 2,60 };6162 const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);6364 await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})65 .balance.transferToSubstrate(alice, bob.address, amount);6667 await helper.wait.newBlocks(waitForBlocks + 1);6869 const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);70 expect(bobsBalanceAfterFirst)71 .to.be.equal(72 bobsBalanceBefore + 1n * amount,73 '#1 Balance of the recipient should be increased by 1 * amount',74 );7576 await helper.wait.newBlocks(periodic.period);7778 const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);79 expect(bobsBalanceAfterSecond)80 .to.be.equal(81 bobsBalanceBefore + 2n * amount,82 '#2 Balance of the recipient should be increased by 2 * amount',83 );84 });8586 itSub.ifWithPallets('Can cancel a scheduled operation which has not yet taken effect', [Pallets.Scheduler], async ({helper}) => {87 const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});88 const token = await collection.mintToken(alice);8990 const scheduledId = await helper.arrange.makeScheduledId();91 const waitForBlocks = 4;9293 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});9495 await token.scheduleAfter(scheduledId, waitForBlocks)96 .transfer(alice, {Substrate: bob.address});9798 await helper.scheduler.cancelScheduled(alice, scheduledId);99100 await helper.wait.newBlocks(waitForBlocks + 1);101102 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});103 });104105 itSub.ifWithPallets('Can cancel a periodic operation (transfer of funds)', [Pallets.Scheduler], async ({helper}) => {106 const waitForBlocks = 1;107 const periodic = {108 period: 3,109 repetitions: 2,110 };111112 const scheduledId = await helper.arrange.makeScheduledId();113114 const amount = 1n * helper.balance.getOneTokenNominal();115116 const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);117118 await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})119 .balance.transferToSubstrate(alice, bob.address, amount);120121 await helper.wait.newBlocks(waitForBlocks + 1);122123 const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);124125 expect(bobsBalanceAfterFirst)126 .to.be.equal(127 bobsBalanceBefore + 1n * amount,128 '#1 Balance of the recipient should be increased by 1 * amount',129 );130131 await helper.scheduler.cancelScheduled(alice, scheduledId);132 await helper.wait.newBlocks(periodic.period);133134 const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);135 expect(bobsBalanceAfterSecond)136 .to.be.equal(137 bobsBalanceAfterFirst,138 '#2 Balance of the recipient should not be changed',139 );140 });141142 itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {143 const scheduledId = await helper.arrange.makeScheduledId();144 const waitForBlocks = 4;145146 const initTestVal = 42;147 const changedTestVal = 111;148149 await helper.testUtils.setTestValue(alice, initTestVal);150151 await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks)152 .testUtils.setTestValueAndRollback(alice, changedTestVal);153154 await helper.wait.newBlocks(waitForBlocks + 1);155156 const testVal = await helper.testUtils.testValue();157 expect(testVal, 'The test value should NOT be commited')158 .to.be.equal(initTestVal);159 });160161 itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.Scheduler, Pallets.TestUtils], async function({helper}) {162 const scheduledId = await helper.arrange.makeScheduledId();163 const waitForBlocks = 4;164 const periodic = {165 period: 2,166 repetitions: 2,167 };168169 const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);170 const scheduledLen = dummyTx.callIndex.length;171172 const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))173 .partialFee.toBigInt();174175 await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})176 .testUtils.justTakeFee(alice);177178 await helper.wait.newBlocks(1);179180 const aliceInitBalance = await helper.balance.getSubstrate(alice.address);181 let diff;182183 await helper.wait.newBlocks(waitForBlocks);184185 const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);186 expect(187 aliceBalanceAfterFirst < aliceInitBalance,188 '[after execution #1] Scheduled task should take a fee',189 ).to.be.true;190191 diff = aliceInitBalance - aliceBalanceAfterFirst;192 expect(diff).to.be.equal(193 expectedScheduledFee,194 'Scheduled task should take the right amount of fees',195 );196197 await helper.wait.newBlocks(periodic.period);198199 const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);200 expect(201 aliceBalanceAfterSecond < aliceBalanceAfterFirst,202 '[after execution #2] Scheduled task should take a fee',203 ).to.be.true;204205 diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;206 expect(diff).to.be.equal(207 expectedScheduledFee,208 'Scheduled task should take the right amount of fees',209 );210 });211212 213 214 itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {215 const currentBlockNumber = await helper.chain.getLatestBlockNumber();216 const blocksBeforeExecution = 10;217 const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;218219 const [220 scheduledId,221 scheduledCancelId,222 ] = await helper.arrange.makeScheduledIds(2);223224 const periodic = {225 period: 5,226 repetitions: 5,227 };228229 const initTestVal = 0;230 const incTestVal = initTestVal + 1;231 const finalTestVal = initTestVal + 2;232233 await helper.testUtils.setTestValue(alice, initTestVal);234235 await helper.scheduler.scheduleAt<DevUniqueHelper>(scheduledId, firstExecutionBlockNumber, {periodic})236 .testUtils.incTestValue(alice);237238 239 240 await helper.scheduler.scheduleAt(241 scheduledCancelId,242 firstExecutionBlockNumber + periodic.period,243 ).scheduler.cancelScheduled(alice, scheduledId);244245 await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);246247 248 expect(await helper.testUtils.testValue())249 .to.be.equal(incTestVal);250251 await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);252253 254 expect(await helper.testUtils.testValue())255 .to.be.equal(finalTestVal);256257 for (let i = 1; i < periodic.repetitions; i++) {258 await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));259 expect(await helper.testUtils.testValue())260 .to.be.equal(finalTestVal);261 }262 });263264 itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {265 const scheduledId = await helper.arrange.makeScheduledId();266 const waitForBlocks = 4;267 const periodic = {268 period: 2,269 repetitions: 5,270 };271272 const initTestVal = 0;273 const maxTestVal = 2;274275 await helper.testUtils.setTestValue(alice, initTestVal);276277 await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})278 .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);279280 await helper.wait.newBlocks(waitForBlocks + 1);281282 283 expect(await helper.testUtils.testValue())284 .to.be.equal(initTestVal + 1);285286 await helper.wait.newBlocks(periodic.period);287288 289 expect(await helper.testUtils.testValue())290 .to.be.equal(initTestVal + 2);291292 await helper.wait.newBlocks(periodic.period);293294 295 expect(await helper.testUtils.testValue())296 .to.be.equal(initTestVal + 2);297 });298299 itSub.ifWithPallets('Root can cancel any scheduled operation', [Pallets.Scheduler], async ({helper}) => {300 const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});301 const token = await collection.mintToken(bob);302303 const scheduledId = await helper.arrange.makeScheduledId();304 const waitForBlocks = 4;305306 await token.scheduleAfter(scheduledId, waitForBlocks)307 .transfer(bob, {Substrate: alice.address});308309 await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId);310311 await helper.wait.newBlocks(waitForBlocks + 1);312313 expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});314 });315316 itSub.ifWithPallets('Root can set prioritized scheduled operation', [Pallets.Scheduler], async ({helper}) => {317 const scheduledId = await helper.arrange.makeScheduledId();318 const waitForBlocks = 4;319320 const amount = 42n * helper.balance.getOneTokenNominal();321322 const balanceBefore = await helper.balance.getSubstrate(charlie.address);323324 await helper.getSudo()325 .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42})326 .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount);327328 await helper.wait.newBlocks(waitForBlocks + 1);329330 const balanceAfter = await helper.balance.getSubstrate(charlie.address);331332 expect(balanceAfter > balanceBefore).to.be.true;333334 const diff = balanceAfter - balanceBefore;335 expect(diff).to.be.equal(amount);336 });337338 itSub.ifWithPallets("Root can change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => {339 const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});340 const token = await collection.mintToken(bob);341342 const scheduledId = await helper.arrange.makeScheduledId();343 const waitForBlocks = 6;344345 await token.scheduleAfter(scheduledId, waitForBlocks)346 .transfer(bob, {Substrate: alice.address});347348 const priority = 112;349 await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority);350351 const priorityChanged = await helper.wait.event(352 waitForBlocks,353 'scheduler',354 'PriorityChanged',355 );356357 expect(priorityChanged !== null).to.be.true;358 expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString());359 });360361 itSub.ifWithPallets('Prioritized operations executes in valid order', [Pallets.Scheduler], async ({helper}) => {362 const [363 scheduledFirstId,364 scheduledSecondId,365 ] = await helper.arrange.makeScheduledIds(2);366367 const currentBlockNumber = await helper.chain.getLatestBlockNumber();368 const blocksBeforeExecution = 6;369 const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;370371 const prioHigh = 0;372 const prioLow = 255;373374 const periodic = {375 period: 6,376 repetitions: 2,377 };378379 const amount = 1n * helper.balance.getOneTokenNominal();380381 382 await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic})383 .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);384385 await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic})386 .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);387388 const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');389390 await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);391392 393 await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh);394 await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow);395396 await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);397398 const dispatchEvents = capture.extractCapturedEvents();399 expect(dispatchEvents.length).to.be.equal(4);400401 const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());402403 const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];404 const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];405406 expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);407 expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);408409 expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);410 expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);411 });412413 itSub.ifWithPallets('Periodic operations always can be rescheduled', [Pallets.Scheduler], async ({helper}) => {414 const maxScheduledPerBlock = 50;415 const numFilledBlocks = 3;416 const ids = await helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);417 const periodicId = ids[0];418 const fillIds = ids.slice(1);419420 const initTestVal = 0;421 const firstExecTestVal = 1;422 const secondExecTestVal = 2;423 await helper.testUtils.setTestValue(alice, initTestVal);424425 const currentBlockNumber = await helper.chain.getLatestBlockNumber();426 const blocksBeforeExecution = 8;427 const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;428429 const period = 5;430431 const periodic = {432 period,433 repetitions: 2,434 };435436 437 const txs = [];438 for (let offset = 0; offset < numFilledBlocks; offset ++) {439 for (let i = 0; i < maxScheduledPerBlock; i++) {440441 const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);442443 const when = firstExecutionBlockNumber + period + offset;444 const tx = helper.constructApiCall('api.tx.scheduler.scheduleNamed', [fillIds[i + offset * maxScheduledPerBlock], when, null, null, scheduledTx]);445446 txs.push(tx);447 }448 }449 await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);450451 await helper.scheduler.scheduleAt<DevUniqueHelper>(periodicId, firstExecutionBlockNumber, {periodic})452 .testUtils.incTestValue(alice);453454 await helper.wait.newBlocks(blocksBeforeExecution);455 expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);456457 await helper.wait.newBlocks(period + numFilledBlocks);458459 460 for (let i = 0; i < numFilledBlocks; i++) {461 expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);462 }463464 465 expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);466 });467});468469describe('Negative Test: Scheduling', () => {470 let alice: IKeyringPair;471 let bob: IKeyringPair;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKeyWrapper) => {475 alice = await privateKeyWrapper('//Alice');476 bob = await privateKeyWrapper('//Bob');477478 await helper.testUtils.enable();479 });480 });481482 itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => {483 const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});484 const token = await collection.mintToken(alice);485486 const scheduledId = await helper.arrange.makeScheduledId();487 const waitForBlocks = 4;488489 await token.scheduleAfter(scheduledId, waitForBlocks)490 .transfer(alice, {Substrate: bob.address});491492 const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);493 await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))494 .to.be.rejectedWith(/scheduler\.FailedToSchedule/);495496 const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);497498 await helper.wait.newBlocks(waitForBlocks + 1);499500 const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);501502 expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});503 expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);504 });505506 itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => {507 const scheduledId = await helper.arrange.makeScheduledId();508 await expect(helper.scheduler.cancelScheduled(alice, scheduledId))509 .to.be.rejectedWith(/scheduler\.NotFound/);510 });511512 itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => {513 const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});514 const token = await collection.mintToken(alice);515516 const scheduledId = await helper.arrange.makeScheduledId();517 const waitForBlocks = 4;518519 await token.scheduleAfter(scheduledId, waitForBlocks)520 .transfer(alice, {Substrate: bob.address});521522 await expect(helper.scheduler.cancelScheduled(bob, scheduledId))523 .to.be.rejectedWith(/BadOrigin/);524525 await helper.wait.newBlocks(waitForBlocks + 1);526527 expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});528 });529530 itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => {531 const scheduledId = await helper.arrange.makeScheduledId();532 const waitForBlocks = 4;533534 const amount = 42n * helper.balance.getOneTokenNominal();535536 const balanceBefore = await helper.balance.getSubstrate(bob.address);537538 const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});539 540 await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))541 .to.be.rejectedWith(/BadOrigin/);542543 await helper.wait.newBlocks(waitForBlocks + 1);544545 const balanceAfter = await helper.balance.getSubstrate(bob.address);546547 expect(balanceAfter).to.be.equal(balanceBefore);548 });549550 itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => {551 const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});552 const token = await collection.mintToken(bob);553554 const scheduledId = await helper.arrange.makeScheduledId();555 const waitForBlocks = 4;556557 await token.scheduleAfter(scheduledId, waitForBlocks)558 .transfer(bob, {Substrate: alice.address});559560 const priority = 112;561 await expect(helper.scheduler.changePriority(alice, scheduledId, priority))562 .to.be.rejectedWith(/BadOrigin/);563564 const priorityChanged = await helper.wait.event(565 waitForBlocks,566 'scheduler',567 'PriorityChanged',568 );569570 expect(priorityChanged === null).to.be.true;571 });572});573574575describe.skip('Sponsoring scheduling', () => {576 577 578579 580 581 582 583 584 585586 it('Can sponsor scheduling a transaction', async () => {587 588 589 590591 592 593 594595 596 597 598 599 600 601 602 603 604 605 });606607 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {608 609 610 611612 613614 615 616 617618 619 620 621622 623 624 625626 627 628 629630 631 632 633 634 635 636 637 638639 640 641642 643 644 });645646 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {647 648649 650 651 652 653654 655 656657 658 659660 661 662663 664 665 666 667668 669 670671 672 673 });674675 it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {676 677 678 679680 681 682683 684 685686 687688 689 690 691692 693 694 695 696697 698699 700 701 });702});