git.delta.rocks / unique-network / refs/commits / 71a39d07af30

difftreelog

Merge pull request #693 from UniqueNetwork/tests/increase_timeout

ut-akuznetsov2022-11-07parents: #1e082e3 #da93be2.patch.diff
in: master
Tests up

15 files changed

addedtests/src/eth/createFTCollection.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/createFTCollection.seqtest.ts
@@ -0,0 +1,76 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets, requirePalletsOrSkip} from '../util';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+
+const DECIMALS = 18;
+
+describe('Create FT collection from EVM', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.Fungible]);
+      donor = await privateKey({filename: __filename});
+    });
+  });
+
+  itEth('Create collection', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    
+    const name = 'CollectionEVM';
+    const description = 'Some description';
+    const prefix = 'token prefix';
+  
+    // todo:playgrounds this might fail when in async environment.
+    const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
+
+    const {collectionId} = await helper.eth.createFungibleCollection(owner, name, DECIMALS, description, prefix);
+    
+    const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
+    const data = (await helper.ft.getData(collectionId))!;
+
+    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
+    expect(collectionId).to.be.eq(collectionCountAfter);
+    expect(data.name).to.be.eq(name);
+    expect(data.description).to.be.eq(description);
+    expect(data.raw.tokenPrefix).to.be.eq(prefix);
+    expect(data.raw.mode).to.be.deep.eq({Fungible: DECIMALS.toString()});
+  });
+
+  // todo:playgrounds this test will fail when in async environment.
+  itEth('Check collection address exist', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
+    const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
+
+    expect(await collectionHelpers.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.false;
+
+    
+    await helper.eth.createFungibleCollection(owner, 'A', DECIMALS, 'A', 'A');
+
+    
+    expect(await collectionHelpers.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.true;
+  });
+});
modifiedtests/src/eth/createFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createFTCollection.test.ts
+++ b/tests/src/eth/createFTCollection.test.ts
@@ -30,50 +30,6 @@
       donor = await privateKey({filename: __filename});
     });
   });
-
-  itEth('Create collection', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-    
-    const name = 'CollectionEVM';
-    const description = 'Some description';
-    const prefix = 'token prefix';
-  
-    // todo:playgrounds this might fail when in async environment.
-    const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-
-    const {collectionId} = await helper.eth.createFungibleCollection(owner, name, DECIMALS, description, prefix);
-    
-    const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-    const data = (await helper.ft.getData(collectionId))!;
-
-    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
-    expect(collectionId).to.be.eq(collectionCountAfter);
-    expect(data.name).to.be.eq(name);
-    expect(data.description).to.be.eq(description);
-    expect(data.raw.tokenPrefix).to.be.eq(prefix);
-    expect(data.raw.mode).to.be.deep.eq({Fungible: DECIMALS.toString()});
-  });
-
-  // todo:playgrounds this test will fail when in async environment.
-  itEth('Check collection address exist', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-
-    const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
-    const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
-    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
-
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.false;
-
-    
-    await helper.eth.createFungibleCollection(owner, 'A', DECIMALS, 'A', 'A');
-
-    
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.true;
-  });
   
   itEth('Set sponsorship', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
addedtests/src/eth/createNFTCollection.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/createNFTCollection.seqtest.ts
@@ -0,0 +1,89 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+
+
+describe('Create NFT collection from EVM', () => {
+  let donor: IKeyringPair;
+
+  before(async function () {
+    await usingEthPlaygrounds(async (_helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+    });
+  });
+
+  itEth('Create collection', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const name = 'CollectionEVM';
+    const description = 'Some description';
+    const prefix = 'token prefix';
+
+    // todo:playgrounds this might fail when in async environment.
+    const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
+    const {collectionId, collectionAddress, events} = await helper.eth.createNFTCollection(owner, name, description, prefix);
+    
+    expect(events).to.be.deep.equal([
+      {
+        address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',
+        event: 'CollectionCreated',
+        args: {
+          owner: owner,
+          collectionId: collectionAddress,
+        },
+      },
+    ]);
+    
+    const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
+
+    const collection = helper.nft.getCollectionObject(collectionId);
+    const data = (await collection.getData())!;
+
+    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
+    expect(collectionId).to.be.eq(collectionCountAfter);
+    expect(data.name).to.be.eq(name);
+    expect(data.description).to.be.eq(description);
+    expect(data.raw.tokenPrefix).to.be.eq(prefix);
+    expect(data.raw.mode).to.be.eq('NFT');
+
+    const options = await collection.getOptions();
+
+    expect(options.tokenPropertyPermissions).to.be.empty;
+  });
+
+  // this test will occasionally fail when in async environment.
+  itEth('Check collection address exist', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
+    const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
+    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
+
+    expect(await collectionHelpers.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.false;
+
+    await collectionHelpers.methods
+      .createNFTCollection('A', 'A', 'A')
+      .send({value: Number(2n * helper.balance.getOneTokenNominal())});
+
+    expect(await collectionHelpers.methods
+      .isCollectionExist(expectedCollectionAddress)
+      .call()).to.be.true;
+  });
+});
modifiedtests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -28,45 +28,6 @@
     });
   });
 
-  itEth('Create collection', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-
-    const name = 'CollectionEVM';
-    const description = 'Some description';
-    const prefix = 'token prefix';
-
-    // todo:playgrounds this might fail when in async environment.
-    const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-    const {collectionId, collectionAddress, events} = await helper.eth.createNFTCollection(owner, name, description, prefix);
-    
-    expect(events).to.be.deep.equal([
-      {
-        address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F',
-        event: 'CollectionCreated',
-        args: {
-          owner: owner,
-          collectionId: collectionAddress,
-        },
-      },
-    ]);
-    
-    const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created;
-
-    const collection = helper.nft.getCollectionObject(collectionId);
-    const data = (await collection.getData())!;
-
-    expect(collectionCountAfter - collectionCountBefore).to.be.eq(1);
-    expect(collectionId).to.be.eq(collectionCountAfter);
-    expect(data.name).to.be.eq(name);
-    expect(data.description).to.be.eq(description);
-    expect(data.raw.tokenPrefix).to.be.eq(prefix);
-    expect(data.raw.mode).to.be.eq('NFT');
-
-    const options = await collection.getOptions();
-
-    expect(options.tokenPropertyPermissions).to.be.empty;
-  });
-
   itEth('Create collection with properties', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
 
@@ -107,27 +68,6 @@
         permission: {mutable: true, collectionAdmin: true, tokenOwner: false},
       },
     ]);
-  });
-
-  // this test will occasionally fail when in async environment.
-  itEth.skip('Check collection address exist', async ({helper}) => {
-    const owner = await helper.eth.createAccountWithBalance(donor);
-
-    const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1;
-    const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId);
-    const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner);
-
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.false;
-
-    await collectionHelpers.methods
-      .createNFTCollection('A', 'A', 'A')
-      .send({value: Number(2n * helper.balance.getOneTokenNominal())});
-
-    expect(await collectionHelpers.methods
-      .isCollectionExist(expectedCollectionAddress)
-      .call()).to.be.true;
   });
 
   itEth('Set sponsorship', async ({helper}) => {
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -52,11 +52,12 @@
 describe('Fungible: Plain calls', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
+  let owner: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([20n], donor);
+      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);
     });
   });
 
@@ -148,9 +149,8 @@
     }
   });
 
-  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
-    const owner = await privateKey('//Alice');
-    const sender = await helper.eth.createAccountWithBalance(donor);
+  itEth('Can perform burnFromCross()', async ({helper}) => {
+    const sender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
 
@@ -261,8 +261,7 @@
   });
 
   itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
-    const owner = await privateKey('//Alice');
-    const sender = await helper.eth.createAccountWithBalance(donor);
+    const sender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
 
@@ -365,11 +364,12 @@
 describe('Fungible: Substrate calls', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
+  let owner: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([20n], donor);
+      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);
     });
   });
 
@@ -455,8 +455,7 @@
   });
 
   itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {
-    const owner = await privateKey('//Alice');
-    const sender = await helper.eth.createAccountWithBalance(donor);
+    const sender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
 
addedtests/src/eth/migration.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/migration.seqtest.ts
@@ -0,0 +1,108 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+describe('EVM Migrations', () => {
+  let superuser: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (_helper, privateKey) => {
+      superuser = await privateKey('//Alice');
+    });
+  });
+  
+  // todo:playgrounds requires sudo, look into later
+  itEth('Deploy contract saved state', async ({helper}) => {
+    /*
+      contract StatefulContract {
+        uint counter;
+        mapping (uint => uint) kv;
+
+        function inc() public {
+          counter = counter + 1;
+        }
+        function counterValue() public view returns (uint) {
+          return counter;
+        }
+
+        function set(uint key, uint value) public {
+          kv[key] = value;
+        }
+
+        function get(uint key) public view returns (uint) {
+          return kv[key];
+        }
+      }
+    */
+    const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6';
+    const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033';
+    const DATA = [
+      // counter = 10
+      ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'],
+      // kv = {1: 1, 2: 2, 3: 3, 4: 4},
+      ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'],
+      ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'],
+      ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'],
+      ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'],
+    ];
+
+    const caller = await helper.eth.createAccountWithBalance(superuser);
+
+    const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]);
+    const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]);
+    const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]);
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled;
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled;
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled;
+
+    const web3 = helper.getWeb3();
+    const contract = new web3.eth.Contract([
+      {
+        inputs: [],
+        name: 'counterValue',
+        outputs: [{
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        }],
+        stateMutability: 'view',
+        type: 'function',
+      },
+      {
+        inputs: [{
+          internalType: 'uint256',
+          name: 'key',
+          type: 'uint256',
+        }],
+        name: 'get',
+        outputs: [{
+          internalType: 'uint256',
+          name: '',
+          type: 'uint256',
+        }],
+        stateMutability: 'view',
+        type: 'function',
+      },
+    ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS});
+
+    expect(await contract.methods.counterValue().call()).to.be.equal('10');
+    for (let i = 1; i <= 4; i++) {
+      expect(await contract.methods.get(i).call()).to.be.equal(i.toString());
+    }
+  });
+});
deletedtests/src/eth/migration.test.tsdiffbeforeafterboth
--- a/tests/src/eth/migration.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {expect, itEth, usingEthPlaygrounds} from './util';
-import {IKeyringPair} from '@polkadot/types/types';
-
-describe('EVM Migrations', () => {
-  let superuser: IKeyringPair;
-
-  before(async function() {
-    await usingEthPlaygrounds(async (_helper, privateKey) => {
-      superuser = await privateKey('//Alice');
-    });
-  });
-  
-  // todo:playgrounds requires sudo, look into later
-  itEth('Deploy contract saved state', async ({helper}) => {
-    /*
-      contract StatefulContract {
-        uint counter;
-        mapping (uint => uint) kv;
-
-        function inc() public {
-          counter = counter + 1;
-        }
-        function counterValue() public view returns (uint) {
-          return counter;
-        }
-
-        function set(uint key, uint value) public {
-          kv[key] = value;
-        }
-
-        function get(uint key) public view returns (uint) {
-          return kv[key];
-        }
-      }
-    */
-    const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6';
-    const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033';
-    const DATA = [
-      // counter = 10
-      ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'],
-      // kv = {1: 1, 2: 2, 3: 3, 4: 4},
-      ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'],
-      ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'],
-      ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'],
-      ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'],
-    ];
-
-    const caller = await helper.eth.createAccountWithBalance(superuser);
-
-    const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]);
-    const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]);
-    const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]);
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled;
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled;
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled;
-
-    const web3 = helper.getWeb3();
-    const contract = new web3.eth.Contract([
-      {
-        inputs: [],
-        name: 'counterValue',
-        outputs: [{
-          internalType: 'uint256',
-          name: '',
-          type: 'uint256',
-        }],
-        stateMutability: 'view',
-        type: 'function',
-      },
-      {
-        inputs: [{
-          internalType: 'uint256',
-          name: 'key',
-          type: 'uint256',
-        }],
-        name: 'get',
-        outputs: [{
-          internalType: 'uint256',
-          name: '',
-          type: 'uint256',
-        }],
-        stateMutability: 'view',
-        type: 'function',
-      },
-    ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS});
-
-    expect(await contract.methods.counterValue().call()).to.be.equal('10');
-    for (let i = 1; i <= 4; i++) {
-      expect(await contract.methods.get(i).call()).to.be.equal(i.toString());
-    }
-  });
-});
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -138,12 +138,14 @@
 
 describe('NFT: Plain calls', () => {
   let donor: IKeyringPair;
-  let alice: IKeyringPair;
+  let minter: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([10n], donor);
+      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
 
@@ -176,8 +178,8 @@
     const caller = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
 
-    const collection = await helper.nft.mintCollection(alice);
-    await collection.addAdmin(alice, {Ethereum: caller});
+    const collection = await helper.nft.mintCollection(minter);
+    await collection.addAdmin(minter, {Ethereum: caller});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);
@@ -208,8 +210,8 @@
   itEth('Can perform burn()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
 
-    const collection = await helper.nft.mintCollection(alice, {});
-    const {tokenId} = await collection.mintToken(alice, {Ethereum: caller});
+    const collection = await helper.nft.mintCollection(minter, {});
+    const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);
@@ -229,8 +231,8 @@
     const owner = await helper.eth.createAccountWithBalance(donor);
     const spender = helper.eth.createAccount();
 
-    const collection = await helper.nft.mintCollection(alice, {});
-    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});
+    const collection = await helper.nft.mintCollection(minter, {});
+    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -246,12 +248,11 @@
     }
   });
 
-  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
-    const minter = await privateKey('//Alice');
+  itEth('Can perform burnFromCross()', async ({helper}) => {
     const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
 
-    const owner = await privateKey('//Bob');
-    const spender = await helper.eth.createAccountWithBalance(donor);
+    const owner = bob;
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const token = await collection.mintToken(minter, {Substrate: owner.address});
 
@@ -276,12 +277,11 @@
     }
   });
 
-  itEth('Can perform approveCross()', async ({helper, privateKey}) => {
-    const minter = await privateKey('//Alice');
+  itEth('Can perform approveCross()', async ({helper}) => {
     const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
 
-    const owner = await helper.eth.createAccountWithBalance(donor);
-    const receiver = await privateKey('//Charlie');
+    const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+    const receiver = charlie;
 
     const token = await collection.mintToken(minter, {Ethereum: owner});
 
@@ -309,8 +309,8 @@
     const spender = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
 
-    const collection = await helper.nft.mintCollection(alice, {});
-    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});
+    const collection = await helper.nft.mintCollection(minter, {});
+    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -373,11 +373,11 @@
   });
 
   itEth('Can perform transfer()', async ({helper}) => {
-    const collection = await helper.nft.mintCollection(alice, {});
+    const collection = await helper.nft.mintCollection(minter, {});
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
 
-    const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});
+    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});
 
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
@@ -407,11 +407,13 @@
 describe('NFT: Fees', () => {
   let donor: IKeyringPair;
   let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       donor = await privateKey({filename: __filename});
-      [alice] = await helper.arrange.createAccounts([10n], donor);
+      [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
     });
   });
 
@@ -443,6 +445,40 @@
     expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
   });
 
+  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+    const collectionMinter = alice;
+    const owner = bob;
+    const receiver = charlie;
+    const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+    const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});
+
+    const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(address, 'nft');
+
+    await token.approve(owner, {Ethereum: spender});
+
+    {
+      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});
+      const event = result.events.Transfer;
+      expect(event).to.be.like({
+        address: helper.ethAddress.fromCollectionId(collection.collectionId),
+        event: 'Transfer',
+        returnValues: {
+          from: helper.address.substrateToEth(owner.address),
+          to: helper.address.substrateToEth(receiver.address),
+          tokenId: token.tokenId.toString(),
+        },
+      });
+    }
+
+    expect(await token.getOwner()).to.be.like({Substrate: receiver.address});
+  });
+
   itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = helper.eth.createAccount();
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -104,12 +104,16 @@
 
 describe('Refungible: Plain calls', () => {
   let donor: IKeyringPair;
+  let minter: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
 
       donor = await privateKey({filename: __filename});
+      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
     });
   });
 
@@ -227,12 +231,11 @@
     }
   });
 
-  itEth('Can perform burnFrom()', async ({helper, privateKey}) => {
-    const minter = await privateKey('//Alice');
+  itEth('Can perform burnFrom()', async ({helper}) => {
     const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
 
-    const owner = await helper.eth.createAccountWithBalance(donor);
-    const spender = await helper.eth.createAccountWithBalance(donor);
+    const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
 
@@ -261,12 +264,11 @@
     expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);
   });
 
-  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
-    const minter = await privateKey('//Alice');
+  itEth('Can perform burnFromCross()', async ({helper}) => {
     const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
-
-    const owner = await privateKey('//Bob');
-    const spender = await helper.eth.createAccountWithBalance(donor);
+    
+    const owner = bob;
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
 
     const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});
 
@@ -294,13 +296,12 @@
     expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);
   });
 
-  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
-    const minter = await privateKey('//Alice');
+  itEth('Can perform transferFromCross()', async ({helper}) => {
     const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
 
-    const owner = await privateKey('//Bob');
-    const spender = await helper.eth.createAccountWithBalance(donor);
-    const receiver = await privateKey('//Charlie');
+    const owner = bob;
+    const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+    const receiver = charlie;
 
     const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});
 
modifiedtests/src/eth/scheduling.test.tsdiffbeforeafterboth
--- a/tests/src/eth/scheduling.test.ts
+++ b/tests/src/eth/scheduling.test.ts
@@ -27,7 +27,8 @@
   });
 
   itEth.ifWithPallets('Successfully schedules and periodically executes an EVM contract', [Pallets.Scheduler], async ({helper, privateKey}) => {
-    const alice = await privateKey('//Alice');
+    const donor = await privateKey({filename: __filename});
+    const [alice] = await helper.arrange.createAccounts([1000n], donor);
 
     const scheduledId = await helper.arrange.makeScheduledId();
 
addedtests/src/inflation.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/inflation.seqtest.ts
@@ -0,0 +1,58 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, usingPlaygrounds} from './util';
+
+// todo:playgrounds requires sudo, look into on the later stage
+describe('integration test: Inflation', () => {
+  let superuser: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (_, privateKey) => {
+      superuser = await privateKey('//Alice');
+    });
+  });
+  
+  itSub('First year inflation is 10%', async ({helper}) => {
+    // Make sure non-sudo can't start inflation
+    const [bob] = await helper.arrange.createAccounts([10n], superuser);
+
+    await expect(helper.executeExtrinsic(bob, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
+
+    // Make sure superuser can't start inflation without explicit sudo
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
+
+    // Start inflation on relay block 1 (Alice is sudo)
+    const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]);
+    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected;
+
+    const blockInterval = (helper.getApi().consts.inflation.inflationBlockInterval as any).toBigInt();
+    const totalIssuanceStart = ((await helper.callRpc('api.query.inflation.startingYearTotalIssuance', [])) as any).toBigInt();
+    const blockInflation = (await helper.callRpc('api.query.inflation.blockInflation', []) as any).toBigInt();
+
+    const YEAR = 5259600n;  // 6-second block. Blocks in one year
+    // const YEAR = 2629800n; // 12-second block. Blocks in one year
+
+    const totalExpectedInflation = totalIssuanceStart / 10n;
+    const totalActualInflation = blockInflation * YEAR / blockInterval;
+
+    const tolerance = 0.00001; // Relative difference per year between theoretical and actual inflation
+    const expectedInflation = totalExpectedInflation / totalActualInflation - 1n;
+
+    expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance);
+  });
+});
deletedtests/src/inflation.test.tsdiffbeforeafterboth
--- a/tests/src/inflation.test.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from './util';
-
-// todo:playgrounds requires sudo, look into on the later stage
-describe('integration test: Inflation', () => {
-  let superuser: IKeyringPair;
-
-  before(async () => {
-    await usingPlaygrounds(async (_, privateKey) => {
-      superuser = await privateKey('//Alice');
-    });
-  });
-  
-  itSub('First year inflation is 10%', async ({helper}) => {
-    // Make sure non-sudo can't start inflation
-    const [bob] = await helper.arrange.createAccounts([10n], superuser);
-
-    await expect(helper.executeExtrinsic(bob, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
-
-    // Make sure superuser can't start inflation without explicit sudo
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/);
-
-    // Start inflation on relay block 1 (Alice is sudo)
-    const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]);
-    await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected;
-
-    const blockInterval = (helper.getApi().consts.inflation.inflationBlockInterval as any).toBigInt();
-    const totalIssuanceStart = ((await helper.callRpc('api.query.inflation.startingYearTotalIssuance', [])) as any).toBigInt();
-    const blockInflation = (await helper.callRpc('api.query.inflation.blockInflation', []) as any).toBigInt();
-
-    const YEAR = 5259600n;  // 6-second block. Blocks in one year
-    // const YEAR = 2629800n; // 12-second block. Blocks in one year
-
-    const totalExpectedInflation = totalIssuanceStart / 10n;
-    const totalActualInflation = blockInflation * YEAR / blockInterval;
-
-    const tolerance = 0.00001; // Relative difference per year between theoretical and actual inflation
-    const expectedInflation = totalExpectedInflation / totalActualInflation - 1n;
-
-    expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance);
-  });
-});
addedtests/src/scheduler.seqtest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/scheduler.seqtest.ts
@@ -0,0 +1,647 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itSub, Pallets, usingPlaygrounds} from './util';
+import {IKeyringPair} from '@polkadot/types/types';
+import {DevUniqueHelper} from './util/playgrounds/unique.dev';
+
+describe('Scheduling token and balance transfers', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKeyWrapper) => {
+      alice = await privateKeyWrapper('//Alice');
+      bob = await privateKeyWrapper('//Bob');
+      charlie = await privateKeyWrapper('//Charlie');
+
+      await helper.testUtils.enable();
+    });
+  });
+
+  itSub.ifWithPallets('Can delay a transfer of an owned token', [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(alice);
+    const schedulerId = await helper.arrange.makeScheduledId();
+    const blocksBeforeExecution = 4;
+
+    await token.scheduleAfter(schedulerId, blocksBeforeExecution)
+      .transfer(alice, {Substrate: bob.address});
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
+
+    await helper.wait.newBlocks(blocksBeforeExecution + 1);
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});
+  });
+
+  itSub.ifWithPallets('Can transfer funds periodically', [Pallets.Scheduler], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 1;
+
+    const amount = 1n * helper.balance.getOneTokenNominal();
+    const periodic = {
+      period: 2,
+      repetitions: 2,
+    };
+
+    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);
+
+    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})
+      .balance.transferToSubstrate(alice, bob.address, amount);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);
+    expect(bobsBalanceAfterFirst)
+      .to.be.equal(
+        bobsBalanceBefore + 1n * amount,
+        '#1 Balance of the recipient should be increased by 1 * amount',
+      );
+
+    await helper.wait.newBlocks(periodic.period);
+
+    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);
+    expect(bobsBalanceAfterSecond)
+      .to.be.equal(
+        bobsBalanceBefore + 2n * amount,
+        '#2 Balance of the recipient should be increased by 2 * amount',
+      );
+  });
+
+  itSub.ifWithPallets('Can cancel a scheduled operation which has not yet taken effect', [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(alice);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(alice, {Substrate: bob.address});
+
+    await helper.scheduler.cancelScheduled(alice, scheduledId);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
+  });
+
+  itSub.ifWithPallets('Can cancel a periodic operation (transfer of funds)', [Pallets.Scheduler], async ({helper}) => {
+    const waitForBlocks = 1;
+    const periodic = {
+      period: 3,
+      repetitions: 2,
+    };
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+
+    const amount = 1n * helper.balance.getOneTokenNominal();
+
+    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);
+
+    await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic})
+      .balance.transferToSubstrate(alice, bob.address, amount);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);
+
+    expect(bobsBalanceAfterFirst)
+      .to.be.equal(
+        bobsBalanceBefore + 1n * amount,
+        '#1 Balance of the recipient should be increased by 1 * amount',
+      );
+
+    await helper.scheduler.cancelScheduled(alice, scheduledId);
+    await helper.wait.newBlocks(periodic.period);
+
+    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);
+    expect(bobsBalanceAfterSecond)
+      .to.be.equal(
+        bobsBalanceAfterFirst,
+        '#2 Balance of the recipient should not be changed',
+      );
+  });
+
+  itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    const initTestVal = 42;
+    const changedTestVal = 111;
+
+    await helper.testUtils.setTestValue(alice, initTestVal);
+
+    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks)
+      .testUtils.setTestValueAndRollback(alice, changedTestVal);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const testVal = await helper.testUtils.testValue();
+    expect(testVal, 'The test value should NOT be commited')
+      .to.be.equal(initTestVal);
+  });
+
+  itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.Scheduler, Pallets.TestUtils], async function({helper}) {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+    const periodic = {
+      period: 2,
+      repetitions: 2,
+    };
+
+    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);
+    const scheduledLen = dummyTx.callIndex.length;
+
+    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))
+      .partialFee.toBigInt();
+
+    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})
+      .testUtils.justTakeFee(alice);
+
+    await helper.wait.newBlocks(1);
+
+    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);
+    let diff;
+
+    await helper.wait.newBlocks(waitForBlocks);
+
+    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);
+    expect(
+      aliceBalanceAfterFirst < aliceInitBalance,
+      '[after execution #1] Scheduled task should take a fee',
+    ).to.be.true;
+
+    diff = aliceInitBalance - aliceBalanceAfterFirst;
+    expect(diff).to.be.equal(
+      expectedScheduledFee,
+      'Scheduled task should take the right amount of fees',
+    );
+
+    await helper.wait.newBlocks(periodic.period);
+
+    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);
+    expect(
+      aliceBalanceAfterSecond < aliceBalanceAfterFirst,
+      '[after execution #2] Scheduled task should take a fee',
+    ).to.be.true;
+
+    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;
+    expect(diff).to.be.equal(
+      expectedScheduledFee,
+      'Scheduled task should take the right amount of fees',
+    );
+  });
+
+  // Check if we can cancel a scheduled periodic operation
+  // in the same block in which it is running
+  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {
+    const currentBlockNumber = await helper.chain.getLatestBlockNumber();
+    const blocksBeforeExecution = 10;
+    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;
+
+    const [
+      scheduledId,
+      scheduledCancelId,
+    ] = await helper.arrange.makeScheduledIds(2);
+
+    const periodic = {
+      period: 5,
+      repetitions: 5,
+    };
+
+    const initTestVal = 0;
+    const incTestVal = initTestVal + 1;
+    const finalTestVal = initTestVal + 2;
+
+    await helper.testUtils.setTestValue(alice, initTestVal);
+
+    await helper.scheduler.scheduleAt<DevUniqueHelper>(scheduledId, firstExecutionBlockNumber, {periodic})
+      .testUtils.incTestValue(alice);
+
+    // Cancel the inc tx after 2 executions
+    // *in the same block* in which the second execution is scheduled
+    await helper.scheduler.scheduleAt(
+      scheduledCancelId,
+      firstExecutionBlockNumber + periodic.period,
+    ).scheduler.cancelScheduled(alice, scheduledId);
+
+    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);
+
+    // execution #0
+    expect(await helper.testUtils.testValue())
+      .to.be.equal(incTestVal);
+
+    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);
+
+    // execution #1
+    expect(await helper.testUtils.testValue())
+      .to.be.equal(finalTestVal);
+
+    for (let i = 1; i < periodic.repetitions; i++) {
+      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));
+      expect(await helper.testUtils.testValue())
+        .to.be.equal(finalTestVal);
+    }
+  });
+
+  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+    const periodic = {
+      period: 2,
+      repetitions: 5,
+    };
+
+    const initTestVal = 0;
+    const maxTestVal = 2;
+
+    await helper.testUtils.setTestValue(alice, initTestVal);
+
+    await helper.scheduler.scheduleAfter<DevUniqueHelper>(scheduledId, waitForBlocks, {periodic})
+      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    // execution #0
+    expect(await helper.testUtils.testValue())
+      .to.be.equal(initTestVal + 1);
+
+    await helper.wait.newBlocks(periodic.period);
+
+    // execution #1
+    expect(await helper.testUtils.testValue())
+      .to.be.equal(initTestVal + 2);
+
+    await helper.wait.newBlocks(periodic.period);
+
+    // <canceled>
+    expect(await helper.testUtils.testValue())
+      .to.be.equal(initTestVal + 2);
+  });
+
+  itSub.ifWithPallets('Root can cancel any scheduled operation', [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(bob);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(bob, {Substrate: alice.address});
+
+    await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});
+  });
+
+  itSub.ifWithPallets('Root can set prioritized scheduled operation', [Pallets.Scheduler], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    const amount = 42n * helper.balance.getOneTokenNominal();
+
+    const balanceBefore = await helper.balance.getSubstrate(charlie.address);
+
+    await helper.getSudo()
+      .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42})
+      .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const balanceAfter = await helper.balance.getSubstrate(charlie.address);
+
+    expect(balanceAfter > balanceBefore).to.be.true;
+
+    const diff = balanceAfter - balanceBefore;
+    expect(diff).to.be.equal(amount);
+  });
+
+  itSub.ifWithPallets("Root can change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(bob);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 6;
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(bob, {Substrate: alice.address});
+
+    const priority = 112;
+    await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority);
+
+    const priorityChanged = await helper.wait.event(
+      waitForBlocks,
+      'scheduler',
+      'PriorityChanged',
+    );
+
+    expect(priorityChanged !== null).to.be.true;
+    expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString());
+  });
+
+  itSub.ifWithPallets('Prioritized operations executes in valid order', [Pallets.Scheduler], async ({helper}) => {
+    const [
+      scheduledFirstId,
+      scheduledSecondId,
+    ] = await helper.arrange.makeScheduledIds(2);
+
+    const currentBlockNumber = await helper.chain.getLatestBlockNumber();
+    const blocksBeforeExecution = 6;
+    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;
+
+    const prioHigh = 0;
+    const prioLow = 255;
+
+    const periodic = {
+      period: 6,
+      repetitions: 2,
+    };
+
+    const amount = 1n * helper.balance.getOneTokenNominal();
+
+    // Scheduler a task with a lower priority first, then with a higher priority
+    await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic})
+      .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);
+
+    await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic})
+      .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount);
+
+    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');
+
+    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);
+
+    // Flip priorities
+    await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh);
+    await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow);
+
+    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);
+
+    const dispatchEvents = capture.extractCapturedEvents();
+    expect(dispatchEvents.length).to.be.equal(4);
+
+    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());
+
+    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];
+    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];
+
+    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);
+    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);
+
+    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);
+    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);
+  });
+});
+
+describe('Negative Test: Scheduling', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+
+  before(async () => {
+    await usingPlaygrounds(async (helper, privateKeyWrapper) => {
+      alice = await privateKeyWrapper('//Alice');
+      bob = await privateKeyWrapper('//Bob');
+
+      await helper.testUtils.enable();
+    });
+  });
+
+  itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(alice);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(alice, {Substrate: bob.address});
+
+    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);
+    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))
+      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);
+
+    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});
+    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);
+  });
+
+  itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))
+      .to.be.rejectedWith(/scheduler\.NotFound/);
+  });
+
+  itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(alice);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(alice, {Substrate: bob.address});
+
+    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))
+      .to.be.rejectedWith(/BadOrigin/);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});
+  });
+
+  itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => {
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    const amount = 42n * helper.balance.getOneTokenNominal();
+
+    const balanceBefore = await helper.balance.getSubstrate(bob.address);
+
+    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});
+    
+    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))
+      .to.be.rejectedWith(/BadOrigin/);
+
+    await helper.wait.newBlocks(waitForBlocks + 1);
+
+    const balanceAfter = await helper.balance.getSubstrate(bob.address);
+
+    expect(balanceAfter).to.be.equal(balanceBefore);
+  });
+
+  itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => {
+    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});
+    const token = await collection.mintToken(bob);
+
+    const scheduledId = await helper.arrange.makeScheduledId();
+    const waitForBlocks = 4;
+
+    await token.scheduleAfter(scheduledId, waitForBlocks)
+      .transfer(bob, {Substrate: alice.address});
+
+    const priority = 112;
+    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
+      .to.be.rejectedWith(/BadOrigin/);
+
+    const priorityChanged = await helper.wait.event(
+      waitForBlocks,
+      'scheduler',
+      'PriorityChanged',
+    );
+
+    expect(priorityChanged === null).to.be.true;
+  });
+});
+
+// Implementation of the functionality tested here was postponed/shelved
+describe.skip('Sponsoring scheduling', () => {
+  // let alice: IKeyringPair;
+  // let bob: IKeyringPair;
+
+  // before(async() => {
+  //   await usingApi(async (_, privateKeyWrapper) => {
+  //     alice = privateKeyWrapper('//Alice');
+  //     bob = privateKeyWrapper('//Bob');
+  //   });
+  // });
+
+  it('Can sponsor scheduling a transaction', async () => {
+    // const collectionId = await createCollectionExpectSuccess();
+    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    // await usingApi(async api => {
+    //   const scheduledId = await makeScheduledId();
+    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+    //   const bobBalanceBefore = await getFreeBalance(bob);
+    //   const waitForBlocks = 4;
+    //   // no need to wait to check, fees must be deducted on scheduling, immediately
+    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);
+    //   const bobBalanceAfter = await getFreeBalance(bob);
+    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;
+    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;
+    //   // wait for sequentiality matters
+    //   await waitNewBlocks(waitForBlocks - 1);
+    // });
+  });
+
+  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {
+    // await usingApi(async (api, privateKeyWrapper) => {
+    //   // Find an empty, unused account
+    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);
+
+    //   const collectionId = await createCollectionExpectSuccess();
+
+    //   // Add zeroBalance address to allow list
+    //   await enablePublicMintingExpectSuccess(alice, collectionId);
+    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+    //   // Grace zeroBalance with money, enough to cover future transactions
+    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+    //   await submitTransactionAsync(alice, balanceTx);
+
+    //   // Mint a fresh NFT
+    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');
+    //   const scheduledId = await makeScheduledId();
+
+    //   // Schedule transfer of the NFT a few blocks ahead
+    //   const waitForBlocks = 5;
+    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);
+
+    //   // Get rid of the account's funds before the scheduled transaction takes place
+    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);
+    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);
+    //   expect(getGenericResult(events).success).to.be.true;
+    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?
+    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);
+    //   const events = await submitTransactionAsync(alice, sudoTx);
+    //   expect(getGenericResult(events).success).to.be.true;*/
+
+    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions
+    //   await waitNewBlocks(waitForBlocks - 3);
+
+    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));
+    // });
+  });
+
+  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {
+    // const collectionId = await createCollectionExpectSuccess();
+
+    // await usingApi(async (api, privateKeyWrapper) => {
+    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);
+    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+    //   await submitTransactionAsync(alice, balanceTx);
+
+    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);
+    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);
+
+    //   const scheduledId = await makeScheduledId();
+    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+    //   const waitForBlocks = 5;
+    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);
+
+    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);
+    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);
+    //   const events = await submitTransactionAsync(alice, sudoTx);
+    //   expect(getGenericResult(events).success).to.be.true;
+
+    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions
+    //   await waitNewBlocks(waitForBlocks - 3);
+
+    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));
+    // });
+  });
+
+  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {
+    // const collectionId = await createCollectionExpectSuccess();
+    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    // await usingApi(async (api, privateKeyWrapper) => {
+    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);
+
+    //   await enablePublicMintingExpectSuccess(alice, collectionId);
+    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+    //   const bobBalanceBefore = await getFreeBalance(bob);
+
+    //   const createData = {nft: {const_data: [], variable_data: []}};
+    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);
+    //   const scheduledId = await makeScheduledId();
+
+    //   /*const badTransaction = async function () {
+    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+    //   };
+    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/
+
+    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);
+
+    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);
+    // });
+  });
+});
deletedtests/src/scheduler.test.tsdiffbeforeafterboth
before · tests/src/scheduler.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itSub, Pallets, 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  // Check if we can cancel a scheduled periodic operation213  // in the same block in which it is running214  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    // Cancel the inc tx after 2 executions239    // *in the same block* in which the second execution is scheduled240    await helper.scheduler.scheduleAt(241      scheduledCancelId,242      firstExecutionBlockNumber + periodic.period,243    ).scheduler.cancelScheduled(alice, scheduledId);244245    await helper.wait.newBlocks(blocksBeforeExecution);246247    // execution #0248    expect(await helper.testUtils.testValue())249      .to.be.equal(incTestVal);250251    await helper.wait.newBlocks(periodic.period);252253    // execution #1254    expect(await helper.testUtils.testValue())255      .to.be.equal(finalTestVal);256257    for (let i = 1; i < periodic.repetitions; i++) {258      await helper.wait.newBlocks(periodic.period);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    // execution #0283    expect(await helper.testUtils.testValue())284      .to.be.equal(initTestVal + 1);285286    await helper.wait.newBlocks(periodic.period);287288    // execution #1289    expect(await helper.testUtils.testValue())290      .to.be.equal(initTestVal + 2);291292    await helper.wait.newBlocks(periodic.period);293294    // <canceled>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 = 4;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    // Scheduler a task with a lower priority first, then with a higher priority382    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.newBlocks(blocksBeforeExecution);391392    // Flip priorities393    await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh);394    await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow);395396    await helper.wait.newBlocks(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  });412});413414describe('Negative Test: Scheduling', () => {415  let alice: IKeyringPair;416  let bob: IKeyringPair;417418  before(async () => {419    await usingPlaygrounds(async (helper, privateKeyWrapper) => {420      alice = await privateKeyWrapper('//Alice');421      bob = await privateKeyWrapper('//Bob');422423      await helper.testUtils.enable();424    });425  });426427  itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => {428    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});429    const token = await collection.mintToken(alice);430431    const scheduledId = await helper.arrange.makeScheduledId();432    const waitForBlocks = 4;433434    await token.scheduleAfter(scheduledId, waitForBlocks)435      .transfer(alice, {Substrate: bob.address});436437    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks);438    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))439      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);440441    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);442443    await helper.wait.newBlocks(waitForBlocks + 1);444445    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);446447    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});448    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);449  });450451  itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => {452    const scheduledId = await helper.arrange.makeScheduledId();453    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))454      .to.be.rejectedWith(/scheduler\.NotFound/);455  });456457  itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => {458    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});459    const token = await collection.mintToken(alice);460461    const scheduledId = await helper.arrange.makeScheduledId();462    const waitForBlocks = 4;463464    await token.scheduleAfter(scheduledId, waitForBlocks)465      .transfer(alice, {Substrate: bob.address});466467    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))468      .to.be.rejectedWith(/BadOrigin/);469470    await helper.wait.newBlocks(waitForBlocks + 1);471472    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});473  });474475  itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => {476    const scheduledId = await helper.arrange.makeScheduledId();477    const waitForBlocks = 4;478479    const amount = 42n * helper.balance.getOneTokenNominal();480481    const balanceBefore = await helper.balance.getSubstrate(bob.address);482483    const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42});484    485    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))486      .to.be.rejectedWith(/BadOrigin/);487488    await helper.wait.newBlocks(waitForBlocks + 1);489490    const balanceAfter = await helper.balance.getSubstrate(bob.address);491492    expect(balanceAfter).to.be.equal(balanceBefore);493  });494495  itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => {496    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});497    const token = await collection.mintToken(bob);498499    const scheduledId = await helper.arrange.makeScheduledId();500    const waitForBlocks = 4;501502    await token.scheduleAfter(scheduledId, waitForBlocks)503      .transfer(bob, {Substrate: alice.address});504505    const priority = 112;506    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))507      .to.be.rejectedWith(/BadOrigin/);508509    const priorityChanged = await helper.wait.event(510      waitForBlocks,511      'scheduler',512      'PriorityChanged',513    );514515    expect(priorityChanged === null).to.be.true;516  });517});518519// Implementation of the functionality tested here was postponed/shelved520describe.skip('Sponsoring scheduling', () => {521  // let alice: IKeyringPair;522  // let bob: IKeyringPair;523524  // before(async() => {525  //   await usingApi(async (_, privateKeyWrapper) => {526  //     alice = privateKeyWrapper('//Alice');527  //     bob = privateKeyWrapper('//Bob');528  //   });529  // });530531  it('Can sponsor scheduling a transaction', async () => {532    // const collectionId = await createCollectionExpectSuccess();533    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);534    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');535536    // await usingApi(async api => {537    //   const scheduledId = await makeScheduledId();538    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);539540    //   const bobBalanceBefore = await getFreeBalance(bob);541    //   const waitForBlocks = 4;542    //   // no need to wait to check, fees must be deducted on scheduling, immediately543    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);544    //   const bobBalanceAfter = await getFreeBalance(bob);545    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;546    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;547    //   // wait for sequentiality matters548    //   await waitNewBlocks(waitForBlocks - 1);549    // });550  });551552  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {553    // await usingApi(async (api, privateKeyWrapper) => {554    //   // Find an empty, unused account555    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);556557    //   const collectionId = await createCollectionExpectSuccess();558559    //   // Add zeroBalance address to allow list560    //   await enablePublicMintingExpectSuccess(alice, collectionId);561    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);562563    //   // Grace zeroBalance with money, enough to cover future transactions564    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);565    //   await submitTransactionAsync(alice, balanceTx);566567    //   // Mint a fresh NFT568    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');569    //   const scheduledId = await makeScheduledId();570571    //   // Schedule transfer of the NFT a few blocks ahead572    //   const waitForBlocks = 5;573    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);574575    //   // Get rid of the account's funds before the scheduled transaction takes place576    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);577    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);578    //   expect(getGenericResult(events).success).to.be.true;579    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?580    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);581    //   const events = await submitTransactionAsync(alice, sudoTx);582    //   expect(getGenericResult(events).success).to.be.true;*/583584    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions585    //   await waitNewBlocks(waitForBlocks - 3);586587    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));588    // });589  });590591  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {592    // const collectionId = await createCollectionExpectSuccess();593594    // await usingApi(async (api, privateKeyWrapper) => {595    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);596    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);597    //   await submitTransactionAsync(alice, balanceTx);598599    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);600    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);601602    //   const scheduledId = await makeScheduledId();603    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);604605    //   const waitForBlocks = 5;606    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);607608    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);609    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);610    //   const events = await submitTransactionAsync(alice, sudoTx);611    //   expect(getGenericResult(events).success).to.be.true;612613    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions614    //   await waitNewBlocks(waitForBlocks - 3);615616    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));617    // });618  });619620  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {621    // const collectionId = await createCollectionExpectSuccess();622    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);623    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');624625    // await usingApi(async (api, privateKeyWrapper) => {626    //   const zeroBalance = await findUnusedAddress(api, privateKeyWrapper);627628    //   await enablePublicMintingExpectSuccess(alice, collectionId);629    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);630631    //   const bobBalanceBefore = await getFreeBalance(bob);632633    //   const createData = {nft: {const_data: [], variable_data: []}};634    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);635    //   const scheduledId = await makeScheduledId();636637    //   /*const badTransaction = async function () {638    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);639    //   };640    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/641642    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);643644    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);645    // });646  });647});
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -422,8 +422,8 @@
     return promise;
   }
 
-  async forParachainBlockNumber(blockNumber: bigint, timeout?: number) {
-    timeout = timeout ?? 300_000;
+  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {
+    timeout = timeout ?? 30 * 60 * 1000;
     // eslint-disable-next-line no-async-promise-executor
     const promise = new Promise<void>(async (resolve) => {
       const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
@@ -437,8 +437,8 @@
     return promise;
   }
   
-  async forRelayBlockNumber(blockNumber: bigint, timeout?: number) {
-    timeout = timeout ?? 300_000;
+  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {
+    timeout = timeout ?? 30 * 60 * 1000;
     // eslint-disable-next-line no-async-promise-executor
     const promise = new Promise<void>(async (resolve) => {
       const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {