difftreelog
Merge pull request #693 from UniqueNetwork/tests/increase_timeout
in: master
Tests up
15 files changed
tests/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;
+ });
+});
tests/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);
tests/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;
+ });
+});
tests/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}) => {
tests/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);
tests/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());
+ }
+ });
+});
tests/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());
- }
- });
-});
tests/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();
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -104,12 +104,16 @@
describe('Refungible: Plain calls', () => {
let donor: IKeyringPair;
+ let minter: IKeyringPair;
+ let bob: IKeyringPair;
+ let charlie: IKeyringPair;
before(async function() {
await usingEthPlaygrounds(async (helper, privateKey) => {
requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
donor = await privateKey({filename: __filename});
+ [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
});
});
@@ -227,12 +231,11 @@
}
});
- itEth('Can perform burnFrom()', async ({helper, privateKey}) => {
- const minter = await privateKey('//Alice');
+ itEth('Can perform burnFrom()', async ({helper}) => {
const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await helper.eth.createAccountWithBalance(donor);
- const spender = await helper.eth.createAccountWithBalance(donor);
+ const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
@@ -261,12 +264,11 @@
expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);
});
- itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
- const minter = await privateKey('//Alice');
+ itEth('Can perform burnFromCross()', async ({helper}) => {
const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
-
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(donor);
+
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});
@@ -294,13 +296,12 @@
expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);
});
- itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
- const minter = await privateKey('//Alice');
+ itEth('Can perform transferFromCross()', async ({helper}) => {
const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(donor);
- const receiver = await privateKey('//Charlie');
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+ const receiver = charlie;
const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});
tests/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();
tests/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);
+ });
+});
tests/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);
- });
-});
tests/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);
+ // });
+ });
+});
tests/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);
- // });
- });
-});
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, TSigner} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';1516export class SilentLogger {17 log(_msg: any, _level: any): void { }18 level = {19 ERROR: 'ERROR' as const,20 WARNING: 'WARNING' as const,21 INFO: 'INFO' as const,22 };23}2425export class SilentConsole {26 // TODO: Remove, this is temporary: Filter unneeded API output27 // (Jaco promised it will be removed in the next version)28 consoleErr: any;29 consoleLog: any;30 consoleWarn: any;3132 constructor() {33 this.consoleErr = console.error;34 this.consoleLog = console.log;35 this.consoleWarn = console.warn;36 }3738 enable() { 39 const outFn = (printer: any) => (...args: any[]) => {40 for (const arg of args) {41 if (typeof arg !== 'string')42 continue;43 if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')44 return;45 }46 printer(...args);47 };48 49 console.error = outFn(this.consoleErr.bind(console));50 console.log = outFn(this.consoleLog.bind(console));51 console.warn = outFn(this.consoleWarn.bind(console));52 }5354 disable() {55 console.error = this.consoleErr;56 console.log = this.consoleLog;57 console.warn = this.consoleWarn;58 }59}6061export class DevUniqueHelper extends UniqueHelper {62 /**63 * Arrange methods for tests64 */65 arrange: ArrangeGroup;66 wait: WaitGroup;67 admin: AdminGroup;68 testUtils: TestUtilGroup;6970 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {71 options.helperBase = options.helperBase ?? DevUniqueHelper;7273 super(logger, options);74 this.arrange = new ArrangeGroup(this);75 this.wait = new WaitGroup(this);76 this.admin = new AdminGroup(this);77 this.testUtils = new TestUtilGroup(this);78 }7980 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {81 const wsProvider = new WsProvider(wsEndpoint);82 this.api = new ApiPromise({83 provider: wsProvider,84 signedExtensions: {85 ContractHelpers: {86 extrinsic: {},87 payload: {},88 },89 FakeTransactionFinalizer: {90 extrinsic: {},91 payload: {},92 },93 },94 rpc: {95 unique: defs.unique.rpc,96 appPromotion: defs.appPromotion.rpc,97 rmrk: defs.rmrk.rpc,98 eth: {99 feeHistory: {100 description: 'Dummy',101 params: [],102 type: 'u8',103 },104 maxPriorityFeePerGas: {105 description: 'Dummy',106 params: [],107 type: 'u8',108 },109 },110 },111 });112 await this.api.isReadyOrError;113 this.network = await UniqueHelper.detectNetwork(this.api);114 }115}116117export class DevRelayHelper extends RelayHelper {}118119export class DevWestmintHelper extends WestmintHelper {120 wait: WaitGroup;121122 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {123 options.helperBase = options.helperBase ?? DevWestmintHelper;124125 super(logger, options);126 this.wait = new WaitGroup(this);127 }128}129130export class DevMoonbeamHelper extends MoonbeamHelper {131 account: MoonbeamAccountGroup;132 wait: WaitGroup;133134 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {135 options.helperBase = options.helperBase ?? DevMoonbeamHelper;136137 super(logger, options);138 this.account = new MoonbeamAccountGroup(this);139 this.wait = new WaitGroup(this);140 }141}142143export class DevMoonriverHelper extends DevMoonbeamHelper {}144145export class DevAcalaHelper extends AcalaHelper {146 wait: WaitGroup;147148 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {149 options.helperBase = options.helperBase ?? DevAcalaHelper;150151 super(logger, options);152 this.wait = new WaitGroup(this);153 }154}155156export class DevKaruraHelper extends DevAcalaHelper {}157158class ArrangeGroup {159 helper: DevUniqueHelper;160161 scheduledIdSlider = 0;162163 constructor(helper: DevUniqueHelper) {164 this.helper = helper;165 }166167 /**168 * Generates accounts with the specified UNQ token balance 169 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.170 * @param donor donor account for balances171 * @returns array of newly created accounts172 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); 173 */174 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {175 let nonce = await this.helper.chain.getNonce(donor.address);176 const wait = new WaitGroup(this.helper);177 const ss58Format = this.helper.chain.getChainProperties().ss58Format;178 const tokenNominal = this.helper.balance.getOneTokenNominal();179 const transactions = [];180 const accounts: IKeyringPair[] = [];181 for (const balance of balances) {182 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);183 accounts.push(recipient);184 if (balance !== 0n) {185 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);186 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));187 nonce++;188 }189 }190191 await Promise.all(transactions).catch(_e => {});192 193 //#region TODO remove this region, when nonce problem will be solved194 const checkBalances = async () => {195 let isSuccess = true;196 for (let i = 0; i < balances.length; i++) {197 const balance = await this.helper.balance.getSubstrate(accounts[i].address);198 if (balance !== balances[i] * tokenNominal) {199 isSuccess = false;200 break;201 }202 }203 return isSuccess;204 };205206 let accountsCreated = false;207 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;208 // checkBalances retry up to 5-50 blocks209 for (let index = 0; index < maxBlocksChecked; index++) {210 accountsCreated = await checkBalances();211 if(accountsCreated) break;212 await wait.newBlocks(1);213 }214215 if (!accountsCreated) throw Error('Accounts generation failed');216 //#endregion217218 return accounts;219 };220221 // TODO combine this method and createAccounts into one222 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => { 223 const createAsManyAsCan = async () => {224 let transactions: any = [];225 const accounts: IKeyringPair[] = [];226 let nonce = await this.helper.chain.getNonce(donor.address);227 const tokenNominal = this.helper.balance.getOneTokenNominal();228 for (let i = 0; i < accountsToCreate; i++) {229 if (i === 500) { // if there are too many accounts to create230 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled 231 transactions = []; //232 nonce = await this.helper.chain.getNonce(donor.address); // update nonce 233 }234 const recepient = this.helper.util.fromSeed(mnemonicGenerate());235 accounts.push(recepient);236 if (withBalance !== 0n) {237 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]);238 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));239 nonce++;240 }241 }242 243 const fullfilledAccounts = [];244 await Promise.allSettled(transactions);245 for (const account of accounts) {246 const accountBalance = await this.helper.balance.getSubstrate(account.address);247 if (accountBalance === withBalance * tokenNominal) {248 fullfilledAccounts.push(account);249 }250 }251 return fullfilledAccounts;252 };253254 255 const crowd: IKeyringPair[] = [];256 // do up to 5 retries257 for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {258 const asManyAsCan = await createAsManyAsCan();259 crowd.push(...asManyAsCan);260 accountsToCreate -= asManyAsCan.length;261 }262263 if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);264265 return crowd;266 };267268 isDevNode = async () => {269 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();270 if (blockNumber == 0) {271 await this.helper.wait.newBlocks(1); 272 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();273 }274 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);275 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);276 const findCreationDate = (block: any) => {277 const humanBlock = block.toHuman();278 let date;279 humanBlock.block.extrinsics.forEach((ext: any) => {280 if(ext.method.section === 'timestamp') {281 date = Number(ext.method.args.now.replaceAll(',', ''));282 }283 });284 return date;285 };286 const block1date = await findCreationDate(block1);287 const block2date = await findCreationDate(block2);288 if(block2date! - block1date! < 9000) return true;289 };290 291 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {292 const address = payer.Substrate ? payer.Substrate : await this.helper.address.ethToSubstrate(payer.Ethereum!);293 let balance = await this.helper.balance.getSubstrate(address); 294 295 await promise();296 297 balance -= await this.helper.balance.getSubstrate(address);298 299 return balance;300 }301302 calculatePalletAddress(palletId: any) {303 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));304 return encodeAddress(address);305 }306307 async makeScheduledIds(num: number): Promise<string[]> {308 await this.helper.wait.noScheduledTasks();309310 function makeId(slider: number) {311 const scheduledIdSize = 32;312 const hexId = slider.toString(16);313 const prefixSize = scheduledIdSize - hexId.length;314315 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;316317 return scheduledId; 318 }319320 const ids = [];321 for (let i = 0; i < num; i++) {322 ids.push(makeId(this.scheduledIdSlider));323 this.scheduledIdSlider += 1;324 }325326 return ids;327 }328329 async makeScheduledId(): Promise<string> {330 return (await this.makeScheduledIds(1))[0];331 }332333 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {334 const capture = new EventCapture(this.helper, eventSection, eventMethod);335 await capture.startCapture();336337 return capture;338 }339}340341class MoonbeamAccountGroup {342 helper: MoonbeamHelper;343344 keyring: Keyring;345 _alithAccount: IKeyringPair;346 _baltatharAccount: IKeyringPair;347 _dorothyAccount: IKeyringPair;348349 constructor(helper: MoonbeamHelper) {350 this.helper = helper;351352 this.keyring = new Keyring({type: 'ethereum'});353 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';354 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';355 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';356357 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');358 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');359 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');360 }361362 alithAccount() {363 return this._alithAccount;364 }365366 baltatharAccount() {367 return this._baltatharAccount;368 }369370 dorothyAccount() {371 return this._dorothyAccount;372 }373374 create() {375 return this.keyring.addFromUri(mnemonicGenerate());376 }377}378379class WaitGroup {380 helper: ChainHelperBase;381382 constructor(helper: ChainHelperBase) {383 this.helper = helper;384 }385386 sleep(milliseconds: number) {387 return new Promise((resolve) => setTimeout(resolve, milliseconds));388 }389390 private async waitWithTimeout(promise: Promise<any>, timeout: number) {391 let isBlock = false;392 promise.then(() => isBlock = true).catch(() => isBlock = true);393 let totalTime = 0;394 const step = 100;395 while(!isBlock) {396 await this.sleep(step);397 totalTime += step;398 if(totalTime >= timeout) throw Error('Blocks production failed');399 }400 return promise;401 }402403 /**404 * Wait for specified number of blocks405 * @param blocksCount number of blocks to wait406 * @returns 407 */408 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {409 timeout = timeout ?? blocksCount * 60_000;410 // eslint-disable-next-line no-async-promise-executor411 const promise = new Promise<void>(async (resolve) => {412 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {413 if (blocksCount > 0) {414 blocksCount--;415 } else {416 unsubscribe();417 resolve();418 }419 });420 });421 await this.waitWithTimeout(promise, timeout);422 return promise;423 }424425 async forParachainBlockNumber(blockNumber: bigint, timeout?: number) {426 timeout = timeout ?? 300_000;427 // eslint-disable-next-line no-async-promise-executor428 const promise = new Promise<void>(async (resolve) => {429 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {430 if (data.number.toNumber() >= blockNumber) {431 unsubscribe();432 resolve();433 }434 });435 });436 await this.waitWithTimeout(promise, timeout);437 return promise;438 }439 440 async forRelayBlockNumber(blockNumber: bigint, timeout?: number) {441 timeout = timeout ?? 300_000;442 // eslint-disable-next-line no-async-promise-executor443 const promise = new Promise<void>(async (resolve) => {444 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {445 if (data.value.relayParentNumber.toNumber() >= blockNumber) {446 // @ts-ignore447 unsubscribe();448 resolve();449 }450 });451 });452 await this.waitWithTimeout(promise, timeout);453 return promise;454 }455456 noScheduledTasks() {457 const api = this.helper.getApi();458 459 // eslint-disable-next-line no-async-promise-executor460 const promise = new Promise<void>(async resolve => {461 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {462 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();463464 if(areThereScheduledTasks.length == 0) {465 unsubscribe();466 resolve();467 }468 }); 469 });470471 return promise;472 }473474 event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {475 // eslint-disable-next-line no-async-promise-executor476 const promise = new Promise<EventRecord | null>(async (resolve) => {477 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {478 const blockNumber = header.number.toHuman();479 const blockHash = header.hash;480 const eventIdStr = `${eventSection}.${eventMethod}`;481 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;482 483 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);484 485 const apiAt = await this.helper.getApi().at(blockHash);486 const eventRecords = (await apiAt.query.system.events()) as any;487 488 const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {489 return r.event.section == eventSection && r.event.method == eventMethod;490 });491 492 if (neededEvent) {493 unsubscribe();494 resolve(neededEvent);495 } else if (maxBlocksToWait > 0) {496 maxBlocksToWait--;497 } else {498 this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);499 unsubscribe();500 resolve(null);501 }502 });503 });504 return promise;505 }506}507508class TestUtilGroup {509 helper: DevUniqueHelper;510511 constructor(helper: DevUniqueHelper) {512 this.helper = helper;513 }514515 async enable() {516 if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {517 return;518 }519520 const signer = this.helper.util.fromSeed('//Alice');521 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);522 }523524 async setTestValue(signer: TSigner, testVal: number) {525 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);526 }527528 async incTestValue(signer: TSigner) {529 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);530 }531532 async setTestValueAndRollback(signer: TSigner, testVal: number) {533 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);534 }535536 async testValue() {537 return (await this.helper.callRpc('api.query.testUtils.testValue', [])).toNumber();538 }539540 async justTakeFee(signer: TSigner) {541 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);542 }543544 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {545 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);546 }547}548549class EventCapture {550 helper: DevUniqueHelper;551 eventSection: string;552 eventMethod: string;553 events: EventRecord[] = [];554 unsubscribe: VoidFn | null = null;555556 constructor(557 helper: DevUniqueHelper,558 eventSection: string,559 eventMethod: string,560 ) {561 this.helper = helper;562 this.eventSection = eventSection;563 this.eventMethod = eventMethod;564 }565566 async startCapture() {567 this.stopCapture();568 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {569 const newEvents = eventRecords.filter(r => {570 return r.event.section == this.eventSection && r.event.method == this.eventMethod;571 });572573 this.events.push(...newEvents);574 })) as any;575 }576577 stopCapture() {578 if (this.unsubscribe !== null) {579 this.unsubscribe();580 }581 }582583 extractCapturedEvents() {584 return this.events;585 }586}587588class AdminGroup {589 helper: UniqueHelper;590591 constructor(helper: UniqueHelper) {592 this.helper = helper;593 }594595 async payoutStakers(signer: IKeyringPair, stakersToPayout: number) {596 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);597 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {598 return {599 staker: e.event.data[0].toString(),600 stake: e.event.data[1].toBigInt(),601 payout: e.event.data[2].toBigInt(),602 };603 });604 }605}