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
before · tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';2021describe('Refungible: Information getting', () => {22  let donor: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);2728      donor = await privateKey({filename: __filename});29    });30  });3132  itEth('totalSupply', async ({helper}) => {33    const caller = await helper.eth.createAccountWithBalance(donor);34    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');35    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);3637    await contract.methods.mint(caller).send();3839    const totalSupply = await contract.methods.totalSupply().call();40    expect(totalSupply).to.equal('1');41  });4243  itEth('balanceOf', async ({helper}) => {44    const caller = await helper.eth.createAccountWithBalance(donor);45    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');46    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);4748    await contract.methods.mint(caller).send();49    await contract.methods.mint(caller).send();50    await contract.methods.mint(caller).send();5152    const balance = await contract.methods.balanceOf(caller).call();53    expect(balance).to.equal('3');54  });5556  itEth('ownerOf', async ({helper}) => {57    const caller = await helper.eth.createAccountWithBalance(donor);58    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');59    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);6061    const result = await contract.methods.mint(caller).send();62    const tokenId = result.events.Transfer.returnValues.tokenId;6364    const owner = await contract.methods.ownerOf(tokenId).call();65    expect(owner).to.equal(caller);66  });6768  itEth('ownerOf after burn', async ({helper}) => {69    const caller = await helper.eth.createAccountWithBalance(donor);70    const receiver = helper.eth.createAccount();71    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');72    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);7374    const result = await contract.methods.mint(caller).send();75    const tokenId = result.events.Transfer.returnValues.tokenId;76    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);7778    await tokenContract.methods.repartition(2).send();79    await tokenContract.methods.transfer(receiver, 1).send();8081    await tokenContract.methods.burnFrom(caller, 1).send();8283    const owner = await contract.methods.ownerOf(tokenId).call();84    expect(owner).to.equal(receiver);85  });8687  itEth('ownerOf for partial ownership', async ({helper}) => {88    const caller = await helper.eth.createAccountWithBalance(donor);89    const receiver = helper.eth.createAccount();90    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');91    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);9293    const result = await contract.methods.mint(caller).send();94    const tokenId = result.events.Transfer.returnValues.tokenId;95    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);9697    await tokenContract.methods.repartition(2).send();98    await tokenContract.methods.transfer(receiver, 1).send();99100    const owner = await contract.methods.ownerOf(tokenId).call();101    expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');102  });103});104105describe('Refungible: Plain calls', () => {106  let donor: IKeyringPair;107108  before(async function() {109    await usingEthPlaygrounds(async (helper, privateKey) => {110      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);111112      donor = await privateKey({filename: __filename});113    });114  });115116  itEth('Can perform mint()', async ({helper}) => {117    const owner = await helper.eth.createAccountWithBalance(donor);118    const receiver = helper.eth.createAccount();119    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');120    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);121122    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();123124    const event = result.events.Transfer;125    expect(event.address).to.equal(collectionAddress);126    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');127    expect(event.returnValues.to).to.equal(receiver);128    const tokenId = event.returnValues.tokenId;129    expect(tokenId).to.be.equal('1');130131    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');132  });133134  itEth.skip('Can perform mintBulk()', async ({helper}) => {135    const owner = await helper.eth.createAccountWithBalance(donor);136    const receiver = helper.eth.createAccount();137    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');138    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);139140    {141      const nextTokenId = await contract.methods.nextTokenId().call();142      expect(nextTokenId).to.be.equal('1');143      const result = await contract.methods.mintBulkWithTokenURI(144        receiver,145        [146          [nextTokenId, 'Test URI 0'],147          [+nextTokenId + 1, 'Test URI 1'],148          [+nextTokenId + 2, 'Test URI 2'],149        ],150      ).send();151152      const events = result.events.Transfer;153      for (let i = 0; i < 2; i++) {154        const event = events[i];155        expect(event.address).to.equal(collectionAddress);156        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');157        expect(event.returnValues.to).to.equal(receiver);158        expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));159      }160161      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');162      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');163      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');164    }165  });166167  itEth('Can perform burn()', async ({helper}) => {168    const caller = await helper.eth.createAccountWithBalance(donor);169    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');170    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);171172    const result = await contract.methods.mint(caller).send();173    const tokenId = result.events.Transfer.returnValues.tokenId;174    {175      const result = await contract.methods.burn(tokenId).send();176      const event = result.events.Transfer;177      expect(event.address).to.equal(collectionAddress);178      expect(event.returnValues.from).to.equal(caller);179      expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');180      expect(event.returnValues.tokenId).to.equal(tokenId.toString());181    }182  });183184  itEth('Can perform transferFrom()', async ({helper}) => {185    const caller = await helper.eth.createAccountWithBalance(donor);186    const receiver = helper.eth.createAccount();187    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');188    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);189190    const result = await contract.methods.mint(caller).send();191    const tokenId = result.events.Transfer.returnValues.tokenId;192193    const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);194195    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller);196    await tokenContract.methods.repartition(15).send();197198    {199      const tokenEvents: any = [];200      tokenContract.events.allEvents((_: any, event: any) => {201        tokenEvents.push(event);202      });203      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();204      if (tokenEvents.length == 0) await helper.wait.newBlocks(1);205206      let event = result.events.Transfer;207      expect(event.address).to.equal(collectionAddress);208      expect(event.returnValues.from).to.equal(caller);209      expect(event.returnValues.to).to.equal(receiver);210      expect(event.returnValues.tokenId).to.equal(tokenId.toString());211212      event = tokenEvents[0];213      expect(event.address).to.equal(tokenAddress);214      expect(event.returnValues.from).to.equal(caller);215      expect(event.returnValues.to).to.equal(receiver);216      expect(event.returnValues.value).to.equal('15');217    }218219    {220      const balance = await contract.methods.balanceOf(receiver).call();221      expect(+balance).to.equal(1);222    }223224    {225      const balance = await contract.methods.balanceOf(caller).call();226      expect(+balance).to.equal(0);227    }228  });229230  itEth('Can perform burnFrom()', async ({helper, privateKey}) => {231    const minter = await privateKey('//Alice');232    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});233234    const owner = await helper.eth.createAccountWithBalance(donor);235    const spender = await helper.eth.createAccountWithBalance(donor);236237    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});238239    const address = helper.ethAddress.fromCollectionId(collection.collectionId);240    const contract = helper.ethNativeContract.collection(address, 'rft');241242    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);243    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);244    await tokenContract.methods.repartition(15).send();245    await tokenContract.methods.approve(spender, 15).send();246247    {248      const result = await contract.methods.burnFrom(owner, token.tokenId).send({from: spender});249      const event = result.events.Transfer;250      expect(event).to.be.like({251        address: helper.ethAddress.fromCollectionId(collection.collectionId),252        event: 'Transfer',253        returnValues: {254          from: owner,255          to: '0x0000000000000000000000000000000000000000',256          tokenId: token.tokenId.toString(),257        },258      });259    }260261    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);262  });263264  itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {265    const minter = await privateKey('//Alice');266    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});267268    const owner = await privateKey('//Bob');269    const spender = await helper.eth.createAccountWithBalance(donor);270271    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});272273    const address = helper.ethAddress.fromCollectionId(collection.collectionId);274    const contract = helper.ethNativeContract.collection(address, 'rft');275276    await token.repartition(owner, 15n);277    await token.approve(owner, {Ethereum: spender}, 15n);278279    {280      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);281      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});282      const event = result.events.Transfer;283      expect(event).to.be.like({284        address: helper.ethAddress.fromCollectionId(collection.collectionId),285        event: 'Transfer',286        returnValues: {287          from: helper.address.substrateToEth(owner.address),288          to: '0x0000000000000000000000000000000000000000',289          tokenId: token.tokenId.toString(),290        },291      });292    }293294    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);295  });296297  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {298    const minter = await privateKey('//Alice');299    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});300301    const owner = await privateKey('//Bob');302    const spender = await helper.eth.createAccountWithBalance(donor);303    const receiver = await privateKey('//Charlie');304305    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});306307    const address = helper.ethAddress.fromCollectionId(collection.collectionId);308    const contract = helper.ethNativeContract.collection(address, 'rft');309310    await token.repartition(owner, 15n);311    await token.approve(owner, {Ethereum: spender}, 15n);312313    {314      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);315      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);316      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});317      const event = result.events.Transfer;318      expect(event).to.be.like({319        address: helper.ethAddress.fromCollectionId(collection.collectionId),320        event: 'Transfer',321        returnValues: {322          from: helper.address.substrateToEth(owner.address),323          to: helper.address.substrateToEth(receiver.address),324          tokenId: token.tokenId.toString(),325        },326      });327    }328329    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);330  });331332  itEth('Can perform transfer()', async ({helper}) => {333    const caller = await helper.eth.createAccountWithBalance(donor);334    const receiver = helper.eth.createAccount();335    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');336    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);337338    const result = await contract.methods.mint(caller).send();339    const tokenId = result.events.Transfer.returnValues.tokenId;340341    {342      const result = await contract.methods.transfer(receiver, tokenId).send();343344      const event = result.events.Transfer;345      expect(event.address).to.equal(collectionAddress);346      expect(event.returnValues.from).to.equal(caller);347      expect(event.returnValues.to).to.equal(receiver);348      expect(event.returnValues.tokenId).to.equal(tokenId.toString());349    }350351    {352      const balance = await contract.methods.balanceOf(caller).call();353      expect(+balance).to.equal(0);354    }355356    {357      const balance = await contract.methods.balanceOf(receiver).call();358      expect(+balance).to.equal(1);359    }360  });361362  itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {363    const caller = await helper.eth.createAccountWithBalance(donor);364    const receiver = helper.eth.createAccount();365    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');366    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);367368    const result = await contract.methods.mint(caller).send();369    const tokenId = result.events.Transfer.returnValues.tokenId;370371    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);372373    await tokenContract.methods.repartition(2).send();374    await tokenContract.methods.transfer(receiver, 1).send();375376    const events: any = [];377    contract.events.allEvents((_: any, event: any) => {378      events.push(event);379    });380381    await tokenContract.methods.transfer(receiver, 1).send();382    if (events.length == 0) await helper.wait.newBlocks(1);383    const event = events[0];384385    expect(event.address).to.equal(collectionAddress);386    expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');387    expect(event.returnValues.to).to.equal(receiver);388    expect(event.returnValues.tokenId).to.equal(tokenId.toString());389  });390391  itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {392    const caller = await helper.eth.createAccountWithBalance(donor);393    const receiver = helper.eth.createAccount();394    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');395    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);396397    const result = await contract.methods.mint(caller).send();398    const tokenId = result.events.Transfer.returnValues.tokenId;399400    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);401402    await tokenContract.methods.repartition(2).send();403404    const events: any = [];405    contract.events.allEvents((_: any, event: any) => {406      events.push(event);407    });408409    await tokenContract.methods.transfer(receiver, 1).send();410    if (events.length == 0) await helper.wait.newBlocks(1);411    const event = events[0];412413    expect(event.address).to.equal(collectionAddress);414    expect(event.returnValues.from).to.equal(caller);415    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');416    expect(event.returnValues.tokenId).to.equal(tokenId.toString());417  });418});419420describe('RFT: Fees', () => {421  let donor: IKeyringPair;422423  before(async function() {424    await usingEthPlaygrounds(async (helper, privateKey) => {425      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);426427      donor = await privateKey({filename: __filename});428    });429  });430431  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {432    const caller = await helper.eth.createAccountWithBalance(donor);433    const receiver = helper.eth.createAccount();434    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');435    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);436437    const result = await contract.methods.mint(caller).send();438    const tokenId = result.events.Transfer.returnValues.tokenId;439440    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());441    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));442    expect(cost > 0n);443  });444445  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {446    const caller = await helper.eth.createAccountWithBalance(donor);447    const receiver = helper.eth.createAccount();448    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');449    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);450451    const result = await contract.methods.mint(caller).send();452    const tokenId = result.events.Transfer.returnValues.tokenId;453454    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());455    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));456    expect(cost > 0n);457  });458});459460describe('Common metadata', () => {461  let donor: IKeyringPair;462  let alice: IKeyringPair;463464  before(async function() {465    await usingEthPlaygrounds(async (helper, privateKey) => {466      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);467468      donor = await privateKey({filename: __filename});469      [alice] = await helper.arrange.createAccounts([20n], donor);470    });471  });472473  itEth('Returns collection name', async ({helper}) => {474    const caller = helper.eth.createAccount();475    const tokenPropertyPermissions = [{476      key: 'URI',477      permission: {478        mutable: true,479        collectionAdmin: true,480        tokenOwner: false,481      },482    }];483    const collection = await helper.rft.mintCollection(484      alice,485      {486        name: 'Leviathan',487        tokenPrefix: '11',488        properties: [{key: 'ERC721Metadata', value: '1'}],489        tokenPropertyPermissions,490      },491    );492493    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);494    const name = await contract.methods.name().call();495    expect(name).to.equal('Leviathan');496  });497498  itEth('Returns symbol name', async ({helper}) => {499    const caller = await helper.eth.createAccountWithBalance(donor);500    const tokenPropertyPermissions = [{501      key: 'URI',502      permission: {503        mutable: true,504        collectionAdmin: true,505        tokenOwner: false,506      },507    }];508    const {collectionId} = await helper.rft.mintCollection(509      alice,510      {511        name: 'Leviathan',512        tokenPrefix: '12',513        properties: [{key: 'ERC721Metadata', value: '1'}],514        tokenPropertyPermissions,515      },516    );517518    const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);519    const symbol = await contract.methods.symbol().call();520    expect(symbol).to.equal('12');521  });522});
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
--- a/tests/src/scheduler.test.ts
+++ /dev/null
@@ -1,647 +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, 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.newBlocks(blocksBeforeExecution);
-
-    // execution #0
-    expect(await helper.testUtils.testValue())
-      .to.be.equal(incTestVal);
-
-    await helper.wait.newBlocks(periodic.period);
-
-    // execution #1
-    expect(await helper.testUtils.testValue())
-      .to.be.equal(finalTestVal);
-
-    for (let i = 1; i < periodic.repetitions; i++) {
-      await helper.wait.newBlocks(periodic.period);
-      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 = 4;
-    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.newBlocks(blocksBeforeExecution);
-
-    // Flip priorities
-    await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh);
-    await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow);
-
-    await helper.wait.newBlocks(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);
-    // });
-  });
-});
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) => {