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.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23 let donor: IKeyringPair;24 let alice: IKeyringPair;2526 before(async function() {27 await usingEthPlaygrounds(async (helper, privateKey) => {28 donor = await privateKey({filename: __filename});29 [alice] = await helper.arrange.createAccounts([10n], donor);30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const collection = await helper.nft.mintCollection(alice, {});35 await collection.mintToken(alice);3637 const caller = await helper.eth.createAccountWithBalance(donor);3839 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40 const totalSupply = await contract.methods.totalSupply().call();4142 expect(totalSupply).to.equal('1');43 });4445 itEth('balanceOf', async ({helper}) => {46 const collection = await helper.nft.mintCollection(alice, {});47 const caller = await helper.eth.createAccountWithBalance(donor);4849 await collection.mintToken(alice, {Ethereum: caller});50 await collection.mintToken(alice, {Ethereum: caller});51 await collection.mintToken(alice, {Ethereum: caller});5253 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54 const balance = await contract.methods.balanceOf(caller).call();5556 expect(balance).to.equal('3');57 });5859 itEth('ownerOf', async ({helper}) => {60 const collection = await helper.nft.mintCollection(alice, {});61 const caller = await helper.eth.createAccountWithBalance(donor);6263 const token = await collection.mintToken(alice, {Ethereum: caller});6465 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667 const owner = await contract.methods.ownerOf(token.tokenId).call();6869 expect(owner).to.equal(caller);70 });7172 itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73 const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74 const caller = helper.eth.createAccount();7576 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778 expect(await contract.methods.name().call()).to.equal('test');79 expect(await contract.methods.symbol().call()).to.equal('TEST');80 });81});8283describe('Check ERC721 token URI for NFT', () => {84 let donor: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (_helper, privateKey) => {88 donor = await privateKey({filename: __filename});89 });90 });9192 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = helper.eth.createAccount();9596 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899 const result = await contract.methods.mint(receiver).send();100 const tokenId = result.events.Transfer.returnValues.tokenId;101 expect(tokenId).to.be.equal('1');102103 if (propertyKey && propertyValue) {104 // Set URL or suffix105 await contract.methods.setProperty(tokenId, propertyKey, Buffer.from(propertyValue)).send();106 }107108 const event = result.events.Transfer;109 expect(event.address).to.be.equal(collectionAddress);110 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111 expect(event.returnValues.to).to.be.equal(receiver);112 expect(event.returnValues.tokenId).to.be.equal(tokenId);113114 return {contract, nextTokenId: tokenId};115 }116117 itEth('Empty tokenURI', async ({helper}) => {118 const {contract, nextTokenId} = await setup(helper, '');119 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120 });121122 itEth('TokenURI from url', async ({helper}) => {123 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125 });126127 itEth('TokenURI from baseURI', async ({helper}) => {128 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130 });131132 itEth('TokenURI from baseURI + suffix', async ({helper}) => {133 const suffix = '/some/suffix';134 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136 });137});138139describe('NFT: Plain calls', () => {140 let donor: IKeyringPair;141 let alice: IKeyringPair;142143 before(async function() {144 await usingEthPlaygrounds(async (helper, privateKey) => {145 donor = await privateKey({filename: __filename});146 [alice] = await helper.arrange.createAccounts([10n], donor);147 });148 });149150 itEth('Can perform mint()', async ({helper}) => {151 const owner = await helper.eth.createAccountWithBalance(donor);152 const receiver = helper.eth.createAccount();153154 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');155 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);156157 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();158 const tokenId = result.events.Transfer.returnValues.tokenId;159 expect(tokenId).to.be.equal('1');160161 const event = result.events.Transfer;162 expect(event.address).to.be.equal(collectionAddress);163 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');164 expect(event.returnValues.to).to.be.equal(receiver);165166 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');167168 // TODO: this wont work right now, need release 919000 first169 // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();170 // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();171 // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);172 });173174 //TODO: CORE-302 add eth methods175 itEth.skip('Can perform mintBulk()', async ({helper}) => {176 const caller = await helper.eth.createAccountWithBalance(donor);177 const receiver = helper.eth.createAccount();178179 const collection = await helper.nft.mintCollection(alice);180 await collection.addAdmin(alice, {Ethereum: caller});181182 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);183 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);184 {185 const bulkSize = 3;186 const nextTokenId = await contract.methods.nextTokenId().call();187 expect(nextTokenId).to.be.equal('1');188 const result = await contract.methods.mintBulkWithTokenURI(189 receiver,190 Array.from({length: bulkSize}, (_, i) => (191 [+nextTokenId + i, `Test URI ${i}`]192 )),193 ).send({from: caller});194195 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);196 for (let i = 0; i < bulkSize; i++) {197 const event = events[i];198 expect(event.address).to.equal(collectionAddress);199 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');200 expect(event.returnValues.to).to.equal(receiver);201 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId+i}`);202203 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);204 }205 }206 });207208 itEth('Can perform burn()', async ({helper}) => {209 const caller = await helper.eth.createAccountWithBalance(donor);210211 const collection = await helper.nft.mintCollection(alice, {});212 const {tokenId} = await collection.mintToken(alice, {Ethereum: caller});213214 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);215 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);216217 {218 const result = await contract.methods.burn(tokenId).send({from: caller});219220 const event = result.events.Transfer;221 expect(event.address).to.be.equal(collectionAddress);222 expect(event.returnValues.from).to.be.equal(caller);223 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');224 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);225 }226 });227228 itEth('Can perform approve()', async ({helper}) => {229 const owner = await helper.eth.createAccountWithBalance(donor);230 const spender = helper.eth.createAccount();231232 const collection = await helper.nft.mintCollection(alice, {});233 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});234235 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);236 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);237238 {239 const result = await contract.methods.approve(spender, tokenId).send({from: owner});240241 const event = result.events.Approval;242 expect(event.address).to.be.equal(collectionAddress);243 expect(event.returnValues.owner).to.be.equal(owner);244 expect(event.returnValues.approved).to.be.equal(spender);245 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);246 }247 });248249 itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {250 const minter = await privateKey('//Alice');251 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});252253 const owner = await privateKey('//Bob');254 const spender = await helper.eth.createAccountWithBalance(donor);255256 const token = await collection.mintToken(minter, {Substrate: owner.address});257258 const address = helper.ethAddress.fromCollectionId(collection.collectionId);259 const contract = helper.ethNativeContract.collection(address, 'nft');260261 {262 await token.approve(owner, {Ethereum: spender});263 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);264 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});265 const events = result.events.Transfer;266267 expect(events).to.be.like({268 address,269 event: 'Transfer',270 returnValues: {271 from: helper.address.substrateToEth(owner.address),272 to: '0x0000000000000000000000000000000000000000',273 tokenId: token.tokenId.toString(),274 },275 });276 }277 });278279 itEth('Can perform approveCross()', async ({helper, privateKey}) => {280 const minter = await privateKey('//Alice');281 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});282283 const owner = await helper.eth.createAccountWithBalance(donor);284 const receiver = await privateKey('//Charlie');285286 const token = await collection.mintToken(minter, {Ethereum: owner});287288 const address = helper.ethAddress.fromCollectionId(collection.collectionId);289 const contract = helper.ethNativeContract.collection(address, 'nft');290291 {292 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);293 const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});294 const event = result.events.Approval;295 expect(event).to.be.like({296 address: helper.ethAddress.fromCollectionId(collection.collectionId),297 event: 'Approval',298 returnValues: {299 owner,300 approved: helper.address.substrateToEth(receiver.address),301 tokenId: token.tokenId.toString(),302 },303 });304 }305 });306307 itEth('Can perform transferFrom()', async ({helper}) => {308 const owner = await helper.eth.createAccountWithBalance(donor);309 const spender = await helper.eth.createAccountWithBalance(donor);310 const receiver = helper.eth.createAccount();311312 const collection = await helper.nft.mintCollection(alice, {});313 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});314315 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318 await contract.methods.approve(spender, tokenId).send({from: owner});319320 {321 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});322323 const event = result.events.Transfer;324 expect(event.address).to.be.equal(collectionAddress);325 expect(event.returnValues.from).to.be.equal(owner);326 expect(event.returnValues.to).to.be.equal(receiver);327 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);328 }329330 {331 const balance = await contract.methods.balanceOf(receiver).call();332 expect(+balance).to.equal(1);333 }334335 {336 const balance = await contract.methods.balanceOf(owner).call();337 expect(+balance).to.equal(0);338 }339 });340341 itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {342 const minter = await privateKey('//Alice');343 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});344345 const owner = await privateKey('//Bob');346 const spender = await helper.eth.createAccountWithBalance(donor);347 const receiver = await privateKey('//Charlie');348349 const token = await collection.mintToken(minter, {Substrate: owner.address});350351 const address = helper.ethAddress.fromCollectionId(collection.collectionId);352 const contract = helper.ethNativeContract.collection(address, 'nft');353354 await token.approve(owner, {Ethereum: spender});355356 {357 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);358 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);359 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});360 const event = result.events.Transfer;361 expect(event).to.be.like({362 address: helper.ethAddress.fromCollectionId(collection.collectionId),363 event: 'Transfer',364 returnValues: {365 from: helper.address.substrateToEth(owner.address),366 to: helper.address.substrateToEth(receiver.address),367 tokenId: token.tokenId.toString(),368 },369 });370 }371372 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});373 });374375 itEth('Can perform transfer()', async ({helper}) => {376 const collection = await helper.nft.mintCollection(alice, {});377 const owner = await helper.eth.createAccountWithBalance(donor);378 const receiver = helper.eth.createAccount();379380 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});381382 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);383 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);384385 {386 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});387388 const event = result.events.Transfer;389 expect(event.address).to.be.equal(collectionAddress);390 expect(event.returnValues.from).to.be.equal(owner);391 expect(event.returnValues.to).to.be.equal(receiver);392 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);393 }394395 {396 const balance = await contract.methods.balanceOf(owner).call();397 expect(+balance).to.equal(0);398 }399400 {401 const balance = await contract.methods.balanceOf(receiver).call();402 expect(+balance).to.equal(1);403 }404 });405});406407describe('NFT: Fees', () => {408 let donor: IKeyringPair;409 let alice: IKeyringPair;410411 before(async function() {412 await usingEthPlaygrounds(async (helper, privateKey) => {413 donor = await privateKey({filename: __filename});414 [alice] = await helper.arrange.createAccounts([10n], donor);415 });416 });417418 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {419 const owner = await helper.eth.createAccountWithBalance(donor);420 const spender = helper.eth.createAccount();421422 const collection = await helper.nft.mintCollection(alice, {});423 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});424425 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);426427 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));428 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));429 });430431 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {432 const owner = await helper.eth.createAccountWithBalance(donor);433 const spender = await helper.eth.createAccountWithBalance(donor);434435 const collection = await helper.nft.mintCollection(alice, {});436 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});437438 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);439440 await contract.methods.approve(spender, tokenId).send({from: owner});441442 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));443 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));444 });445446 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {447 const owner = await helper.eth.createAccountWithBalance(donor);448 const receiver = helper.eth.createAccount();449450 const collection = await helper.nft.mintCollection(alice, {});451 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});452453 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);454455 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));456 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));457 });458});459460describe('NFT: Substrate calls', () => {461 let donor: IKeyringPair;462 let alice: IKeyringPair;463464 before(async function() {465 await usingEthPlaygrounds(async (helper, privateKey) => {466 donor = await privateKey({filename: __filename});467 [alice] = await helper.arrange.createAccounts([20n], donor);468 });469 });470471 itEth('Events emitted for mint()', async ({helper}) => {472 const collection = await helper.nft.mintCollection(alice, {});473 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);474 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');475476 const events: any = [];477 contract.events.allEvents((_: any, event: any) => {478 events.push(event);479 });480481 const {tokenId} = await collection.mintToken(alice);482 if (events.length == 0) await helper.wait.newBlocks(1);483 const event = events[0];484485 expect(event.event).to.be.equal('Transfer');486 expect(event.address).to.be.equal(collectionAddress);487 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');488 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));489 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());490 });491492 itEth('Events emitted for burn()', async ({helper}) => {493 const collection = await helper.nft.mintCollection(alice, {});494 const token = await collection.mintToken(alice);495496 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);497 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');498499 const events: any = [];500 contract.events.allEvents((_: any, event: any) => {501 events.push(event);502 });503504 await token.burn(alice);505 if (events.length == 0) await helper.wait.newBlocks(1);506 const event = events[0];507508 expect(event.event).to.be.equal('Transfer');509 expect(event.address).to.be.equal(collectionAddress);510 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));511 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');512 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());513 });514515 itEth('Events emitted for approve()', async ({helper}) => {516 const receiver = helper.eth.createAccount();517518 const collection = await helper.nft.mintCollection(alice, {});519 const token = await collection.mintToken(alice);520521 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);522 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');523524 const events: any = [];525 contract.events.allEvents((_: any, event: any) => {526 events.push(event);527 });528529 await token.approve(alice, {Ethereum: receiver});530 if (events.length == 0) await helper.wait.newBlocks(1);531 const event = events[0];532533 expect(event.event).to.be.equal('Approval');534 expect(event.address).to.be.equal(collectionAddress);535 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));536 expect(event.returnValues.approved).to.be.equal(receiver);537 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());538 });539540 itEth('Events emitted for transferFrom()', async ({helper}) => {541 const [bob] = await helper.arrange.createAccounts([10n], donor);542 const receiver = helper.eth.createAccount();543544 const collection = await helper.nft.mintCollection(alice, {});545 const token = await collection.mintToken(alice);546 await token.approve(alice, {Substrate: bob.address});547548 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);549 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');550551 const events: any = [];552 contract.events.allEvents((_: any, event: any) => {553 events.push(event);554 });555556 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});557558 if (events.length == 0) await helper.wait.newBlocks(1);559 const event = events[0];560561 expect(event.address).to.be.equal(collectionAddress);562 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));563 expect(event.returnValues.to).to.be.equal(receiver);564 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);565 });566567 itEth('Events emitted for transfer()', async ({helper}) => {568 const receiver = helper.eth.createAccount();569570 const collection = await helper.nft.mintCollection(alice, {});571 const token = await collection.mintToken(alice);572573 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);574 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');575576 const events: any = [];577 contract.events.allEvents((_: any, event: any) => {578 events.push(event);579 });580581 await token.transfer(alice, {Ethereum: receiver});582583 if (events.length == 0) await helper.wait.newBlocks(1);584 const event = events[0];585586 expect(event.address).to.be.equal(collectionAddress);587 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));588 expect(event.returnValues.to).to.be.equal(receiver);589 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);590 });591});592593describe('Common metadata', () => {594 let donor: IKeyringPair;595 let alice: IKeyringPair;596597 before(async function() {598 await usingEthPlaygrounds(async (helper, privateKey) => {599 donor = await privateKey({filename: __filename});600 [alice] = await helper.arrange.createAccounts([20n], donor);601 });602 });603604 itEth('Returns collection name', async ({helper}) => {605 const caller = await helper.eth.createAccountWithBalance(donor);606 const tokenPropertyPermissions = [{607 key: 'URI',608 permission: {609 mutable: true,610 collectionAdmin: true,611 tokenOwner: false,612 },613 }];614 const collection = await helper.nft.mintCollection(615 alice,616 {617 name: 'oh River',618 tokenPrefix: 'CHANGE',619 properties: [{key: 'ERC721Metadata', value: '1'}],620 tokenPropertyPermissions,621 },622 );623624 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);625 const name = await contract.methods.name().call();626 expect(name).to.equal('oh River');627 });628629 itEth('Returns symbol name', async ({helper}) => {630 const caller = await helper.eth.createAccountWithBalance(donor);631 const tokenPropertyPermissions = [{632 key: 'URI',633 permission: {634 mutable: true,635 collectionAdmin: true,636 tokenOwner: false,637 },638 }];639 const collection = await helper.nft.mintCollection(640 alice,641 {642 name: 'oh River',643 tokenPrefix: 'CHANGE',644 properties: [{key: 'ERC721Metadata', value: '1'}],645 tokenPropertyPermissions,646 },647 );648649 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);650 const symbol = await contract.methods.symbol().call();651 expect(symbol).to.equal('CHANGE');652 });653});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23 let donor: IKeyringPair;24 let alice: IKeyringPair;2526 before(async function() {27 await usingEthPlaygrounds(async (helper, privateKey) => {28 donor = await privateKey({filename: __filename});29 [alice] = await helper.arrange.createAccounts([10n], donor);30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const collection = await helper.nft.mintCollection(alice, {});35 await collection.mintToken(alice);3637 const caller = await helper.eth.createAccountWithBalance(donor);3839 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40 const totalSupply = await contract.methods.totalSupply().call();4142 expect(totalSupply).to.equal('1');43 });4445 itEth('balanceOf', async ({helper}) => {46 const collection = await helper.nft.mintCollection(alice, {});47 const caller = await helper.eth.createAccountWithBalance(donor);4849 await collection.mintToken(alice, {Ethereum: caller});50 await collection.mintToken(alice, {Ethereum: caller});51 await collection.mintToken(alice, {Ethereum: caller});5253 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54 const balance = await contract.methods.balanceOf(caller).call();5556 expect(balance).to.equal('3');57 });5859 itEth('ownerOf', async ({helper}) => {60 const collection = await helper.nft.mintCollection(alice, {});61 const caller = await helper.eth.createAccountWithBalance(donor);6263 const token = await collection.mintToken(alice, {Ethereum: caller});6465 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667 const owner = await contract.methods.ownerOf(token.tokenId).call();6869 expect(owner).to.equal(caller);70 });7172 itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73 const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74 const caller = helper.eth.createAccount();7576 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778 expect(await contract.methods.name().call()).to.equal('test');79 expect(await contract.methods.symbol().call()).to.equal('TEST');80 });81});8283describe('Check ERC721 token URI for NFT', () => {84 let donor: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (_helper, privateKey) => {88 donor = await privateKey({filename: __filename});89 });90 });9192 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = helper.eth.createAccount();9596 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899 const result = await contract.methods.mint(receiver).send();100 const tokenId = result.events.Transfer.returnValues.tokenId;101 expect(tokenId).to.be.equal('1');102103 if (propertyKey && propertyValue) {104 // Set URL or suffix105 await contract.methods.setProperty(tokenId, propertyKey, Buffer.from(propertyValue)).send();106 }107108 const event = result.events.Transfer;109 expect(event.address).to.be.equal(collectionAddress);110 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111 expect(event.returnValues.to).to.be.equal(receiver);112 expect(event.returnValues.tokenId).to.be.equal(tokenId);113114 return {contract, nextTokenId: tokenId};115 }116117 itEth('Empty tokenURI', async ({helper}) => {118 const {contract, nextTokenId} = await setup(helper, '');119 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120 });121122 itEth('TokenURI from url', async ({helper}) => {123 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125 });126127 itEth('TokenURI from baseURI', async ({helper}) => {128 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130 });131132 itEth('TokenURI from baseURI + suffix', async ({helper}) => {133 const suffix = '/some/suffix';134 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136 });137});138139describe('NFT: Plain calls', () => {140 let donor: IKeyringPair;141 let minter: IKeyringPair;142 let bob: IKeyringPair;143 let charlie: IKeyringPair;144145 before(async function() {146 await usingEthPlaygrounds(async (helper, privateKey) => {147 donor = await privateKey({filename: __filename});148 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);149 });150 });151152 itEth('Can perform mint()', async ({helper}) => {153 const owner = await helper.eth.createAccountWithBalance(donor);154 const receiver = helper.eth.createAccount();155156 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');157 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);158159 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();160 const tokenId = result.events.Transfer.returnValues.tokenId;161 expect(tokenId).to.be.equal('1');162163 const event = result.events.Transfer;164 expect(event.address).to.be.equal(collectionAddress);165 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');166 expect(event.returnValues.to).to.be.equal(receiver);167168 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');169170 // TODO: this wont work right now, need release 919000 first171 // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();172 // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();173 // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);174 });175176 //TODO: CORE-302 add eth methods177 itEth.skip('Can perform mintBulk()', async ({helper}) => {178 const caller = await helper.eth.createAccountWithBalance(donor);179 const receiver = helper.eth.createAccount();180181 const collection = await helper.nft.mintCollection(minter);182 await collection.addAdmin(minter, {Ethereum: caller});183184 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);185 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);186 {187 const bulkSize = 3;188 const nextTokenId = await contract.methods.nextTokenId().call();189 expect(nextTokenId).to.be.equal('1');190 const result = await contract.methods.mintBulkWithTokenURI(191 receiver,192 Array.from({length: bulkSize}, (_, i) => (193 [+nextTokenId + i, `Test URI ${i}`]194 )),195 ).send({from: caller});196197 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);198 for (let i = 0; i < bulkSize; i++) {199 const event = events[i];200 expect(event.address).to.equal(collectionAddress);201 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');202 expect(event.returnValues.to).to.equal(receiver);203 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId+i}`);204205 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);206 }207 }208 });209210 itEth('Can perform burn()', async ({helper}) => {211 const caller = await helper.eth.createAccountWithBalance(donor);212213 const collection = await helper.nft.mintCollection(minter, {});214 const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});215216 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);217 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);218219 {220 const result = await contract.methods.burn(tokenId).send({from: caller});221222 const event = result.events.Transfer;223 expect(event.address).to.be.equal(collectionAddress);224 expect(event.returnValues.from).to.be.equal(caller);225 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');226 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);227 }228 });229230 itEth('Can perform approve()', async ({helper}) => {231 const owner = await helper.eth.createAccountWithBalance(donor);232 const spender = helper.eth.createAccount();233234 const collection = await helper.nft.mintCollection(minter, {});235 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});236237 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);238 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);239240 {241 const result = await contract.methods.approve(spender, tokenId).send({from: owner});242243 const event = result.events.Approval;244 expect(event.address).to.be.equal(collectionAddress);245 expect(event.returnValues.owner).to.be.equal(owner);246 expect(event.returnValues.approved).to.be.equal(spender);247 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);248 }249 });250251 itEth('Can perform burnFromCross()', async ({helper}) => {252 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});253254 const owner = bob;255 const spender = await helper.eth.createAccountWithBalance(donor, 100n);256257 const token = await collection.mintToken(minter, {Substrate: owner.address});258259 const address = helper.ethAddress.fromCollectionId(collection.collectionId);260 const contract = helper.ethNativeContract.collection(address, 'nft');261262 {263 await token.approve(owner, {Ethereum: spender});264 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);265 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});266 const events = result.events.Transfer;267268 expect(events).to.be.like({269 address,270 event: 'Transfer',271 returnValues: {272 from: helper.address.substrateToEth(owner.address),273 to: '0x0000000000000000000000000000000000000000',274 tokenId: token.tokenId.toString(),275 },276 });277 }278 });279280 itEth('Can perform approveCross()', async ({helper}) => {281 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});282283 const owner = await helper.eth.createAccountWithBalance(donor, 100n);284 const receiver = charlie;285286 const token = await collection.mintToken(minter, {Ethereum: owner});287288 const address = helper.ethAddress.fromCollectionId(collection.collectionId);289 const contract = helper.ethNativeContract.collection(address, 'nft');290291 {292 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);293 const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});294 const event = result.events.Approval;295 expect(event).to.be.like({296 address: helper.ethAddress.fromCollectionId(collection.collectionId),297 event: 'Approval',298 returnValues: {299 owner,300 approved: helper.address.substrateToEth(receiver.address),301 tokenId: token.tokenId.toString(),302 },303 });304 }305 });306307 itEth('Can perform transferFrom()', async ({helper}) => {308 const owner = await helper.eth.createAccountWithBalance(donor);309 const spender = await helper.eth.createAccountWithBalance(donor);310 const receiver = helper.eth.createAccount();311312 const collection = await helper.nft.mintCollection(minter, {});313 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});314315 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);316 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317318 await contract.methods.approve(spender, tokenId).send({from: owner});319320 {321 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});322323 const event = result.events.Transfer;324 expect(event.address).to.be.equal(collectionAddress);325 expect(event.returnValues.from).to.be.equal(owner);326 expect(event.returnValues.to).to.be.equal(receiver);327 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);328 }329330 {331 const balance = await contract.methods.balanceOf(receiver).call();332 expect(+balance).to.equal(1);333 }334335 {336 const balance = await contract.methods.balanceOf(owner).call();337 expect(+balance).to.equal(0);338 }339 });340341 itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {342 const minter = await privateKey('//Alice');343 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});344345 const owner = await privateKey('//Bob');346 const spender = await helper.eth.createAccountWithBalance(donor);347 const receiver = await privateKey('//Charlie');348349 const token = await collection.mintToken(minter, {Substrate: owner.address});350351 const address = helper.ethAddress.fromCollectionId(collection.collectionId);352 const contract = helper.ethNativeContract.collection(address, 'nft');353354 await token.approve(owner, {Ethereum: spender});355356 {357 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);358 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);359 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});360 const event = result.events.Transfer;361 expect(event).to.be.like({362 address: helper.ethAddress.fromCollectionId(collection.collectionId),363 event: 'Transfer',364 returnValues: {365 from: helper.address.substrateToEth(owner.address),366 to: helper.address.substrateToEth(receiver.address),367 tokenId: token.tokenId.toString(),368 },369 });370 }371372 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});373 });374375 itEth('Can perform transfer()', async ({helper}) => {376 const collection = await helper.nft.mintCollection(minter, {});377 const owner = await helper.eth.createAccountWithBalance(donor);378 const receiver = helper.eth.createAccount();379380 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});381382 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);383 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);384385 {386 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});387388 const event = result.events.Transfer;389 expect(event.address).to.be.equal(collectionAddress);390 expect(event.returnValues.from).to.be.equal(owner);391 expect(event.returnValues.to).to.be.equal(receiver);392 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);393 }394395 {396 const balance = await contract.methods.balanceOf(owner).call();397 expect(+balance).to.equal(0);398 }399400 {401 const balance = await contract.methods.balanceOf(receiver).call();402 expect(+balance).to.equal(1);403 }404 });405});406407describe('NFT: Fees', () => {408 let donor: IKeyringPair;409 let alice: IKeyringPair;410 let bob: IKeyringPair;411 let charlie: IKeyringPair;412413 before(async function() {414 await usingEthPlaygrounds(async (helper, privateKey) => {415 donor = await privateKey({filename: __filename});416 [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);417 });418 });419420 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {421 const owner = await helper.eth.createAccountWithBalance(donor);422 const spender = helper.eth.createAccount();423424 const collection = await helper.nft.mintCollection(alice, {});425 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});426427 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);428429 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));430 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));431 });432433 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {434 const owner = await helper.eth.createAccountWithBalance(donor);435 const spender = await helper.eth.createAccountWithBalance(donor);436437 const collection = await helper.nft.mintCollection(alice, {});438 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});439440 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);441442 await contract.methods.approve(spender, tokenId).send({from: owner});443444 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));445 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));446 });447448 itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {449 const collectionMinter = alice;450 const owner = bob;451 const receiver = charlie;452 const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});453454 const spender = await helper.eth.createAccountWithBalance(donor, 100n);455456 const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});457458 const address = helper.ethAddress.fromCollectionId(collection.collectionId);459 const contract = helper.ethNativeContract.collection(address, 'nft');460461 await token.approve(owner, {Ethereum: spender});462463 {464 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);465 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);466 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});467 const event = result.events.Transfer;468 expect(event).to.be.like({469 address: helper.ethAddress.fromCollectionId(collection.collectionId),470 event: 'Transfer',471 returnValues: {472 from: helper.address.substrateToEth(owner.address),473 to: helper.address.substrateToEth(receiver.address),474 tokenId: token.tokenId.toString(),475 },476 });477 }478479 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});480 });481482 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {483 const owner = await helper.eth.createAccountWithBalance(donor);484 const receiver = helper.eth.createAccount();485486 const collection = await helper.nft.mintCollection(alice, {});487 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});488489 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);490491 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));492 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));493 });494});495496describe('NFT: Substrate calls', () => {497 let donor: IKeyringPair;498 let alice: IKeyringPair;499500 before(async function() {501 await usingEthPlaygrounds(async (helper, privateKey) => {502 donor = await privateKey({filename: __filename});503 [alice] = await helper.arrange.createAccounts([20n], donor);504 });505 });506507 itEth('Events emitted for mint()', async ({helper}) => {508 const collection = await helper.nft.mintCollection(alice, {});509 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);510 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');511512 const events: any = [];513 contract.events.allEvents((_: any, event: any) => {514 events.push(event);515 });516517 const {tokenId} = await collection.mintToken(alice);518 if (events.length == 0) await helper.wait.newBlocks(1);519 const event = events[0];520521 expect(event.event).to.be.equal('Transfer');522 expect(event.address).to.be.equal(collectionAddress);523 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');524 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));525 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());526 });527528 itEth('Events emitted for burn()', async ({helper}) => {529 const collection = await helper.nft.mintCollection(alice, {});530 const token = await collection.mintToken(alice);531532 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);533 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');534535 const events: any = [];536 contract.events.allEvents((_: any, event: any) => {537 events.push(event);538 });539540 await token.burn(alice);541 if (events.length == 0) await helper.wait.newBlocks(1);542 const event = events[0];543544 expect(event.event).to.be.equal('Transfer');545 expect(event.address).to.be.equal(collectionAddress);546 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));547 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');548 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());549 });550551 itEth('Events emitted for approve()', async ({helper}) => {552 const receiver = helper.eth.createAccount();553554 const collection = await helper.nft.mintCollection(alice, {});555 const token = await collection.mintToken(alice);556557 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);558 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');559560 const events: any = [];561 contract.events.allEvents((_: any, event: any) => {562 events.push(event);563 });564565 await token.approve(alice, {Ethereum: receiver});566 if (events.length == 0) await helper.wait.newBlocks(1);567 const event = events[0];568569 expect(event.event).to.be.equal('Approval');570 expect(event.address).to.be.equal(collectionAddress);571 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));572 expect(event.returnValues.approved).to.be.equal(receiver);573 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());574 });575576 itEth('Events emitted for transferFrom()', async ({helper}) => {577 const [bob] = await helper.arrange.createAccounts([10n], donor);578 const receiver = helper.eth.createAccount();579580 const collection = await helper.nft.mintCollection(alice, {});581 const token = await collection.mintToken(alice);582 await token.approve(alice, {Substrate: bob.address});583584 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);585 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');586587 const events: any = [];588 contract.events.allEvents((_: any, event: any) => {589 events.push(event);590 });591592 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});593594 if (events.length == 0) await helper.wait.newBlocks(1);595 const event = events[0];596597 expect(event.address).to.be.equal(collectionAddress);598 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));599 expect(event.returnValues.to).to.be.equal(receiver);600 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);601 });602603 itEth('Events emitted for transfer()', async ({helper}) => {604 const receiver = helper.eth.createAccount();605606 const collection = await helper.nft.mintCollection(alice, {});607 const token = await collection.mintToken(alice);608609 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);610 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');611612 const events: any = [];613 contract.events.allEvents((_: any, event: any) => {614 events.push(event);615 });616617 await token.transfer(alice, {Ethereum: receiver});618619 if (events.length == 0) await helper.wait.newBlocks(1);620 const event = events[0];621622 expect(event.address).to.be.equal(collectionAddress);623 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));624 expect(event.returnValues.to).to.be.equal(receiver);625 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);626 });627});628629describe('Common metadata', () => {630 let donor: IKeyringPair;631 let alice: IKeyringPair;632633 before(async function() {634 await usingEthPlaygrounds(async (helper, privateKey) => {635 donor = await privateKey({filename: __filename});636 [alice] = await helper.arrange.createAccounts([20n], donor);637 });638 });639640 itEth('Returns collection name', async ({helper}) => {641 const caller = await helper.eth.createAccountWithBalance(donor);642 const tokenPropertyPermissions = [{643 key: 'URI',644 permission: {645 mutable: true,646 collectionAdmin: true,647 tokenOwner: false,648 },649 }];650 const collection = await helper.nft.mintCollection(651 alice,652 {653 name: 'oh River',654 tokenPrefix: 'CHANGE',655 properties: [{key: 'ERC721Metadata', value: '1'}],656 tokenPropertyPermissions,657 },658 );659660 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);661 const name = await contract.methods.name().call();662 expect(name).to.equal('oh River');663 });664665 itEth('Returns symbol name', async ({helper}) => {666 const caller = await helper.eth.createAccountWithBalance(donor);667 const tokenPropertyPermissions = [{668 key: 'URI',669 permission: {670 mutable: true,671 collectionAdmin: true,672 tokenOwner: false,673 },674 }];675 const collection = await helper.nft.mintCollection(676 alice,677 {678 name: 'oh River',679 tokenPrefix: 'CHANGE',680 properties: [{key: 'ERC721Metadata', value: '1'}],681 tokenPropertyPermissions,682 },683 );684685 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);686 const symbol = await contract.methods.symbol().call();687 expect(symbol).to.equal('CHANGE');688 });689});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.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -422,8 +422,8 @@
return promise;
}
- async forParachainBlockNumber(blockNumber: bigint, timeout?: number) {
- timeout = timeout ?? 300_000;
+ async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {
+ timeout = timeout ?? 30 * 60 * 1000;
// eslint-disable-next-line no-async-promise-executor
const promise = new Promise<void>(async (resolve) => {
const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
@@ -437,8 +437,8 @@
return promise;
}
- async forRelayBlockNumber(blockNumber: bigint, timeout?: number) {
- timeout = timeout ?? 300_000;
+ async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {
+ timeout = timeout ?? 30 * 60 * 1000;
// eslint-disable-next-line no-async-promise-executor
const promise = new Promise<void>(async (resolve) => {
const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {