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