difftreelog
Split tests to sequential and parallel, remove Alice and friends
in: master
14 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.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Information getting', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;2324 before(async function() {25 await usingEthPlaygrounds(async (helper, privateKey) => {26 donor = await privateKey({filename: __filename});27 [alice] = await helper.arrange.createAccounts([20n], donor);28 });29 });3031 itEth('totalSupply', async ({helper}) => {32 const caller = await helper.eth.createAccountWithBalance(donor);33 const collection = await helper.ft.mintCollection(alice);34 await collection.mint(alice, 200n);3536 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);37 const totalSupply = await contract.methods.totalSupply().call();38 expect(totalSupply).to.equal('200');39 });4041 itEth('balanceOf', async ({helper}) => {42 const caller = await helper.eth.createAccountWithBalance(donor);43 const collection = await helper.ft.mintCollection(alice);44 await collection.mint(alice, 200n, {Ethereum: caller});4546 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);47 const balance = await contract.methods.balanceOf(caller).call();48 expect(balance).to.equal('200');49 });50});5152describe('Fungible: Plain calls', () => {53 let donor: IKeyringPair;54 let alice: IKeyringPair;5556 before(async function() {57 await usingEthPlaygrounds(async (helper, privateKey) => {58 donor = await privateKey({filename: __filename});59 [alice] = await helper.arrange.createAccounts([20n], donor);60 });61 });6263 itEth('Can perform mint()', async ({helper}) => {64 const owner = await helper.eth.createAccountWithBalance(donor);65 const receiver = helper.eth.createAccount();66 const collection = await helper.ft.mintCollection(alice);67 await collection.addAdmin(alice, {Ethereum: owner});6869 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);70 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7172 const result = await contract.methods.mint(receiver, 100).send();73 74 const event = result.events.Transfer;75 expect(event.address).to.equal(collectionAddress);76 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');77 expect(event.returnValues.to).to.equal(receiver);78 expect(event.returnValues.value).to.equal('100');79 });8081 itEth('Can perform mintBulk()', async ({helper}) => {82 const owner = await helper.eth.createAccountWithBalance(donor);83 const bulkSize = 3;84 const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());85 const collection = await helper.ft.mintCollection(alice);86 await collection.addAdmin(alice, {Ethereum: owner});8788 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);89 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);9091 const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (92 [receivers[i], (i + 1) * 10]93 ))).send();94 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);95 for (let i = 0; i < bulkSize; i++) {96 const event = events[i];97 expect(event.address).to.equal(collectionAddress);98 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');99 expect(event.returnValues.to).to.equal(receivers[i]);100 expect(event.returnValues.value).to.equal(String(10 * (i + 1)));101 }102 });103104 itEth('Can perform burn()', async ({helper}) => {105 const owner = await helper.eth.createAccountWithBalance(donor);106 const receiver = await helper.eth.createAccountWithBalance(donor);107 const collection = await helper.ft.mintCollection(alice);108 await collection.addAdmin(alice, {Ethereum: owner});109110 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);111 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);112 await contract.methods.mint(receiver, 100).send();113114 const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});115 116 const event = result.events.Transfer;117 expect(event.address).to.equal(collectionAddress);118 expect(event.returnValues.from).to.equal(receiver);119 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');120 expect(event.returnValues.value).to.equal('49');121122 const balance = await contract.methods.balanceOf(receiver).call();123 expect(balance).to.equal('51');124 });125126 itEth('Can perform approve()', async ({helper}) => {127 const owner = await helper.eth.createAccountWithBalance(donor);128 const spender = helper.eth.createAccount();129 const collection = await helper.ft.mintCollection(alice);130 await collection.mint(alice, 200n, {Ethereum: owner});131132 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);133 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);134135 {136 const result = await contract.methods.approve(spender, 100).send({from: owner});137138 const event = result.events.Approval;139 expect(event.address).to.be.equal(collectionAddress);140 expect(event.returnValues.owner).to.be.equal(owner);141 expect(event.returnValues.spender).to.be.equal(spender);142 expect(event.returnValues.value).to.be.equal('100');143 }144145 {146 const allowance = await contract.methods.allowance(owner, spender).call();147 expect(+allowance).to.equal(100);148 }149 });150151 itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {152 const alice = await privateKey('//Alice');153 const sender = await helper.eth.createAccountWithBalance(alice, 100n);154155 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);156157 await collection.mint(alice, 200n, {Substrate: alice.address});158 await collection.approveTokens(alice, {Ethereum: sender}, 100n);159160 const address = helper.ethAddress.fromCollectionId(collection.collectionId);161 const contract = helper.ethNativeContract.collection(address, 'ft');162163 const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});164 165 const ownerCross = helper.ethCrossAccount.fromKeyringPair(alice);166 const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});167 const events = result.events;168169 expect(events).to.be.like({170 Transfer: {171 address: helper.ethAddress.fromCollectionId(collection.collectionId),172 event: 'Transfer',173 returnValues: {174 from: helper.address.substrateToEth(alice.address),175 to: '0x0000000000000000000000000000000000000000',176 value: '49',177 },178 },179 Approval: {180 address: helper.ethAddress.fromCollectionId(collection.collectionId),181 returnValues: {182 owner: helper.address.substrateToEth(alice.address),183 spender: sender,184 value: '51',185 },186 event: 'Approval',187 },188 });189190 const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});191 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);192 });193194 itEth('Can perform transferFrom()', async ({helper}) => {195 const owner = await helper.eth.createAccountWithBalance(donor);196 const spender = await helper.eth.createAccountWithBalance(donor);197 const receiver = helper.eth.createAccount();198 const collection = await helper.ft.mintCollection(alice);199 await collection.mint(alice, 200n, {Ethereum: owner});200201 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);202 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);203204 await contract.methods.approve(spender, 100).send();205206 {207 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});208 209 let event = result.events.Transfer;210 expect(event.address).to.be.equal(collectionAddress);211 expect(event.returnValues.from).to.be.equal(owner);212 expect(event.returnValues.to).to.be.equal(receiver);213 expect(event.returnValues.value).to.be.equal('49');214215 event = result.events.Approval;216 expect(event.address).to.be.equal(collectionAddress);217 expect(event.returnValues.owner).to.be.equal(owner);218 expect(event.returnValues.spender).to.be.equal(spender);219 expect(event.returnValues.value).to.be.equal('51');220 }221222 {223 const balance = await contract.methods.balanceOf(receiver).call();224 expect(+balance).to.equal(49);225 }226227 {228 const balance = await contract.methods.balanceOf(owner).call();229 expect(+balance).to.equal(151);230 }231 });232233 itEth('Can perform transfer()', async ({helper}) => {234 const owner = await helper.eth.createAccountWithBalance(donor);235 const receiver = await helper.eth.createAccountWithBalance(donor);236 const collection = await helper.ft.mintCollection(alice);237 await collection.mint(alice, 200n, {Ethereum: owner});238239 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);240 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);241242 {243 const result = await contract.methods.transfer(receiver, 50).send({from: owner});244 245 const event = result.events.Transfer;246 expect(event.address).to.be.equal(collectionAddress);247 expect(event.returnValues.from).to.be.equal(owner);248 expect(event.returnValues.to).to.be.equal(receiver);249 expect(event.returnValues.value).to.be.equal('50');250 }251252 {253 const balance = await contract.methods.balanceOf(owner).call();254 expect(+balance).to.equal(150);255 }256257 {258 const balance = await contract.methods.balanceOf(receiver).call();259 expect(+balance).to.equal(50);260 }261 });262263 itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {264 const alice = await privateKey('//Alice');265 const sender = await helper.eth.createAccountWithBalance(alice, 100n);266267 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);268269 const receiver = helper.eth.createAccount();270271 await collection.mint(alice, 200n, {Substrate: alice.address});272 await collection.approveTokens(alice, {Ethereum: sender}, 100n);273274 const address = helper.ethAddress.fromCollectionId(collection.collectionId);275 const contract = helper.ethNativeContract.collection(address, 'ft');276277 const from = helper.ethCrossAccount.fromKeyringPair(alice);278 const to = helper.ethCrossAccount.fromAddress(receiver);279280 const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});281 const toBalanceBefore = await collection.getBalance({Ethereum: receiver});282 283 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});284285 expect(result.events).to.be.like({286 Transfer: {287 address,288 event: 'Transfer',289 returnValues: {290 from: helper.address.substrateToEth(alice.address),291 to: receiver,292 value: '51',293 },294 },295 Approval: {296 address,297 event: 'Approval',298 returnValues: {299 owner: helper.address.substrateToEth(alice.address),300 spender: sender,301 value: '49',302 },303 }});304305 const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});306 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);307 const toBalanceAfter = await collection.getBalance({Ethereum: receiver});308 expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);309 });310});311312describe('Fungible: Fees', () => {313 let donor: IKeyringPair;314 let alice: IKeyringPair;315316 before(async function() {317 await usingEthPlaygrounds(async (helper, privateKey) => {318 donor = await privateKey({filename: __filename});319 [alice] = await helper.arrange.createAccounts([20n], donor);320 });321 });322 323 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {324 const owner = await helper.eth.createAccountWithBalance(donor);325 const spender = helper.eth.createAccount();326 const collection = await helper.ft.mintCollection(alice);327 await collection.mint(alice, 200n, {Ethereum: owner});328329 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);330 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);331332 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));333 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));334 });335336 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {337 const owner = await helper.eth.createAccountWithBalance(donor);338 const spender = await helper.eth.createAccountWithBalance(donor);339 const collection = await helper.ft.mintCollection(alice);340 await collection.mint(alice, 200n, {Ethereum: owner});341342 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);343 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);344345 await contract.methods.approve(spender, 100).send({from: owner});346347 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));348 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));349 });350351 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {352 const owner = await helper.eth.createAccountWithBalance(donor);353 const receiver = helper.eth.createAccount();354 const collection = await helper.ft.mintCollection(alice);355 await collection.mint(alice, 200n, {Ethereum: owner});356357 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);358 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);359360 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));361 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));362 });363});364365describe('Fungible: Substrate calls', () => {366 let donor: IKeyringPair;367 let alice: IKeyringPair;368369 before(async function() {370 await usingEthPlaygrounds(async (helper, privateKey) => {371 donor = await privateKey({filename: __filename});372 [alice] = await helper.arrange.createAccounts([20n], donor);373 });374 });375376 itEth('Events emitted for approve()', async ({helper}) => {377 const receiver = helper.eth.createAccount();378 const collection = await helper.ft.mintCollection(alice);379 await collection.mint(alice, 200n);380381 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);382 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');383384 const events: any = [];385 contract.events.allEvents((_: any, event: any) => {386 events.push(event);387 });388 389 await collection.approveTokens(alice, {Ethereum: receiver}, 100n);390 if (events.length == 0) await helper.wait.newBlocks(1);391 const event = events[0];392393 expect(event.event).to.be.equal('Approval');394 expect(event.address).to.be.equal(collectionAddress);395 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));396 expect(event.returnValues.spender).to.be.equal(receiver);397 expect(event.returnValues.value).to.be.equal('100');398 });399400 itEth('Events emitted for transferFrom()', async ({helper}) => {401 const [bob] = await helper.arrange.createAccounts([10n], donor);402 const receiver = helper.eth.createAccount();403 const collection = await helper.ft.mintCollection(alice);404 await collection.mint(alice, 200n);405 await collection.approveTokens(alice, {Substrate: bob.address}, 100n);406407 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);408 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');409410 const events: any = [];411 contract.events.allEvents((_: any, event: any) => {412 events.push(event);413 });414415 await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);416 if (events.length == 0) await helper.wait.newBlocks(1);417 let event = events[0];418419 expect(event.event).to.be.equal('Transfer');420 expect(event.address).to.be.equal(collectionAddress);421 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));422 expect(event.returnValues.to).to.be.equal(receiver);423 expect(event.returnValues.value).to.be.equal('51');424425 event = events[1];426 expect(event.event).to.be.equal('Approval');427 expect(event.address).to.be.equal(collectionAddress);428 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));429 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));430 expect(event.returnValues.value).to.be.equal('49');431 });432433 itEth('Events emitted for transfer()', async ({helper}) => {434 const receiver = helper.eth.createAccount();435 const collection = await helper.ft.mintCollection(alice);436 await collection.mint(alice, 200n);437438 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);439 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');440441 const events: any = [];442 contract.events.allEvents((_: any, event: any) => {443 events.push(event);444 });445 446 await collection.transfer(alice, {Ethereum:receiver}, 51n);447 if (events.length == 0) await helper.wait.newBlocks(1);448 const event = events[0];449450 expect(event.event).to.be.equal('Transfer');451 expect(event.address).to.be.equal(collectionAddress);452 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));453 expect(event.returnValues.to).to.be.equal(receiver);454 expect(event.returnValues.value).to.be.equal('51');455 });456457 itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {458 const alice = await privateKey('//Alice');459 const sender = await helper.eth.createAccountWithBalance(alice, 100n);460461 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);462463 const receiver = helper.eth.createAccount();464465 await collection.mint(alice, 200n, {Substrate: alice.address});466 await collection.approveTokens(alice, {Ethereum: sender}, 100n);467468 const address = helper.ethAddress.fromCollectionId(collection.collectionId);469 const contract = helper.ethNativeContract.collection(address, 'ft');470471 const from = helper.ethCrossAccount.fromKeyringPair(alice);472 const to = helper.ethCrossAccount.fromAddress(receiver);473 474 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});475476 expect(result.events).to.be.like({477 Transfer: {478 address,479 event: 'Transfer',480 returnValues: {481 from: helper.address.substrateToEth(alice.address),482 to: receiver,483 value: '51',484 },485 },486 Approval: {487 address,488 event: 'Approval',489 returnValues: {490 owner: helper.address.substrateToEth(alice.address),491 spender: sender,492 value: '49',493 },494 }});495 });496});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Information getting', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;2324 before(async function() {25 await usingEthPlaygrounds(async (helper, privateKey) => {26 donor = await privateKey({filename: __filename});27 [alice] = await helper.arrange.createAccounts([20n], donor);28 });29 });3031 itEth('totalSupply', async ({helper}) => {32 const caller = await helper.eth.createAccountWithBalance(donor);33 const collection = await helper.ft.mintCollection(alice);34 await collection.mint(alice, 200n);3536 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);37 const totalSupply = await contract.methods.totalSupply().call();38 expect(totalSupply).to.equal('200');39 });4041 itEth('balanceOf', async ({helper}) => {42 const caller = await helper.eth.createAccountWithBalance(donor);43 const collection = await helper.ft.mintCollection(alice);44 await collection.mint(alice, 200n, {Ethereum: caller});4546 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);47 const balance = await contract.methods.balanceOf(caller).call();48 expect(balance).to.equal('200');49 });50});5152describe('Fungible: Plain calls', () => {53 let donor: IKeyringPair;54 let alice: IKeyringPair;5556 before(async function() {57 await usingEthPlaygrounds(async (helper, privateKey) => {58 donor = await privateKey({filename: __filename});59 [alice] = await helper.arrange.createAccounts([20n], donor);60 });61 });6263 itEth('Can perform mint()', async ({helper}) => {64 const owner = await helper.eth.createAccountWithBalance(donor);65 const receiver = helper.eth.createAccount();66 const collection = await helper.ft.mintCollection(alice);67 await collection.addAdmin(alice, {Ethereum: owner});6869 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);70 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7172 const result = await contract.methods.mint(receiver, 100).send();73 74 const event = result.events.Transfer;75 expect(event.address).to.equal(collectionAddress);76 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');77 expect(event.returnValues.to).to.equal(receiver);78 expect(event.returnValues.value).to.equal('100');79 });8081 itEth('Can perform mintBulk()', async ({helper}) => {82 const owner = await helper.eth.createAccountWithBalance(donor);83 const bulkSize = 3;84 const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());85 const collection = await helper.ft.mintCollection(alice);86 await collection.addAdmin(alice, {Ethereum: owner});8788 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);89 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);9091 const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (92 [receivers[i], (i + 1) * 10]93 ))).send();94 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);95 for (let i = 0; i < bulkSize; i++) {96 const event = events[i];97 expect(event.address).to.equal(collectionAddress);98 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');99 expect(event.returnValues.to).to.equal(receivers[i]);100 expect(event.returnValues.value).to.equal(String(10 * (i + 1)));101 }102 });103104 itEth('Can perform burn()', async ({helper}) => {105 const owner = await helper.eth.createAccountWithBalance(donor);106 const receiver = await helper.eth.createAccountWithBalance(donor);107 const collection = await helper.ft.mintCollection(alice);108 await collection.addAdmin(alice, {Ethereum: owner});109110 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);111 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);112 await contract.methods.mint(receiver, 100).send();113114 const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});115 116 const event = result.events.Transfer;117 expect(event.address).to.equal(collectionAddress);118 expect(event.returnValues.from).to.equal(receiver);119 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');120 expect(event.returnValues.value).to.equal('49');121122 const balance = await contract.methods.balanceOf(receiver).call();123 expect(balance).to.equal('51');124 });125126 itEth('Can perform approve()', async ({helper}) => {127 const owner = await helper.eth.createAccountWithBalance(donor);128 const spender = helper.eth.createAccount();129 const collection = await helper.ft.mintCollection(alice);130 await collection.mint(alice, 200n, {Ethereum: owner});131132 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);133 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);134135 {136 const result = await contract.methods.approve(spender, 100).send({from: owner});137138 const event = result.events.Approval;139 expect(event.address).to.be.equal(collectionAddress);140 expect(event.returnValues.owner).to.be.equal(owner);141 expect(event.returnValues.spender).to.be.equal(spender);142 expect(event.returnValues.value).to.be.equal('100');143 }144145 {146 const allowance = await contract.methods.allowance(owner, spender).call();147 expect(+allowance).to.equal(100);148 }149 });150151 itEth('Can perform burnFromCross()', async ({helper}) => {152 const sender = await helper.eth.createAccountWithBalance(donor, 100n);153154 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);155156 await collection.mint(alice, 200n, {Substrate: alice.address});157 await collection.approveTokens(alice, {Ethereum: sender}, 100n);158159 const address = helper.ethAddress.fromCollectionId(collection.collectionId);160 const contract = helper.ethNativeContract.collection(address, 'ft');161162 const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});163 164 const ownerCross = helper.ethCrossAccount.fromKeyringPair(alice);165 const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});166 const events = result.events;167168 expect(events).to.be.like({169 Transfer: {170 address: helper.ethAddress.fromCollectionId(collection.collectionId),171 event: 'Transfer',172 returnValues: {173 from: helper.address.substrateToEth(alice.address),174 to: '0x0000000000000000000000000000000000000000',175 value: '49',176 },177 },178 Approval: {179 address: helper.ethAddress.fromCollectionId(collection.collectionId),180 returnValues: {181 owner: helper.address.substrateToEth(alice.address),182 spender: sender,183 value: '51',184 },185 event: 'Approval',186 },187 });188189 const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});190 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);191 });192193 itEth('Can perform transferFrom()', async ({helper}) => {194 const owner = await helper.eth.createAccountWithBalance(donor);195 const spender = await helper.eth.createAccountWithBalance(donor);196 const receiver = helper.eth.createAccount();197 const collection = await helper.ft.mintCollection(alice);198 await collection.mint(alice, 200n, {Ethereum: owner});199200 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);201 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);202203 await contract.methods.approve(spender, 100).send();204205 {206 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});207 208 let event = result.events.Transfer;209 expect(event.address).to.be.equal(collectionAddress);210 expect(event.returnValues.from).to.be.equal(owner);211 expect(event.returnValues.to).to.be.equal(receiver);212 expect(event.returnValues.value).to.be.equal('49');213214 event = result.events.Approval;215 expect(event.address).to.be.equal(collectionAddress);216 expect(event.returnValues.owner).to.be.equal(owner);217 expect(event.returnValues.spender).to.be.equal(spender);218 expect(event.returnValues.value).to.be.equal('51');219 }220221 {222 const balance = await contract.methods.balanceOf(receiver).call();223 expect(+balance).to.equal(49);224 }225226 {227 const balance = await contract.methods.balanceOf(owner).call();228 expect(+balance).to.equal(151);229 }230 });231232 itEth('Can perform transfer()', async ({helper}) => {233 const owner = await helper.eth.createAccountWithBalance(donor);234 const receiver = await helper.eth.createAccountWithBalance(donor);235 const collection = await helper.ft.mintCollection(alice);236 await collection.mint(alice, 200n, {Ethereum: owner});237238 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);239 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);240241 {242 const result = await contract.methods.transfer(receiver, 50).send({from: owner});243 244 const event = result.events.Transfer;245 expect(event.address).to.be.equal(collectionAddress);246 expect(event.returnValues.from).to.be.equal(owner);247 expect(event.returnValues.to).to.be.equal(receiver);248 expect(event.returnValues.value).to.be.equal('50');249 }250251 {252 const balance = await contract.methods.balanceOf(owner).call();253 expect(+balance).to.equal(150);254 }255256 {257 const balance = await contract.methods.balanceOf(receiver).call();258 expect(+balance).to.equal(50);259 }260 });261262 itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {263 const sender = await helper.eth.createAccountWithBalance(donor, 100n);264265 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);266267 const receiver = helper.eth.createAccount();268269 await collection.mint(alice, 200n, {Substrate: alice.address});270 await collection.approveTokens(alice, {Ethereum: sender}, 100n);271272 const address = helper.ethAddress.fromCollectionId(collection.collectionId);273 const contract = helper.ethNativeContract.collection(address, 'ft');274275 const from = helper.ethCrossAccount.fromKeyringPair(alice);276 const to = helper.ethCrossAccount.fromAddress(receiver);277278 const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});279 const toBalanceBefore = await collection.getBalance({Ethereum: receiver});280 281 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});282283 expect(result.events).to.be.like({284 Transfer: {285 address,286 event: 'Transfer',287 returnValues: {288 from: helper.address.substrateToEth(alice.address),289 to: receiver,290 value: '51',291 },292 },293 Approval: {294 address,295 event: 'Approval',296 returnValues: {297 owner: helper.address.substrateToEth(alice.address),298 spender: sender,299 value: '49',300 },301 }});302303 const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});304 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);305 const toBalanceAfter = await collection.getBalance({Ethereum: receiver});306 expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);307 });308});309310describe('Fungible: Fees', () => {311 let donor: IKeyringPair;312 let alice: IKeyringPair;313314 before(async function() {315 await usingEthPlaygrounds(async (helper, privateKey) => {316 donor = await privateKey({filename: __filename});317 [alice] = await helper.arrange.createAccounts([20n], donor);318 });319 });320 321 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {322 const owner = await helper.eth.createAccountWithBalance(donor);323 const spender = helper.eth.createAccount();324 const collection = await helper.ft.mintCollection(alice);325 await collection.mint(alice, 200n, {Ethereum: owner});326327 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);328 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);329330 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));331 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));332 });333334 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {335 const owner = await helper.eth.createAccountWithBalance(donor);336 const spender = await helper.eth.createAccountWithBalance(donor);337 const collection = await helper.ft.mintCollection(alice);338 await collection.mint(alice, 200n, {Ethereum: owner});339340 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);341 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);342343 await contract.methods.approve(spender, 100).send({from: owner});344345 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));346 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));347 });348349 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {350 const owner = await helper.eth.createAccountWithBalance(donor);351 const receiver = helper.eth.createAccount();352 const collection = await helper.ft.mintCollection(alice);353 await collection.mint(alice, 200n, {Ethereum: owner});354355 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);356 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);357358 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));359 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));360 });361});362363describe('Fungible: Substrate calls', () => {364 let donor: IKeyringPair;365 let alice: IKeyringPair;366367 before(async function() {368 await usingEthPlaygrounds(async (helper, privateKey) => {369 donor = await privateKey({filename: __filename});370 [alice] = await helper.arrange.createAccounts([20n], donor);371 });372 });373374 itEth('Events emitted for approve()', async ({helper}) => {375 const receiver = helper.eth.createAccount();376 const collection = await helper.ft.mintCollection(alice);377 await collection.mint(alice, 200n);378379 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);380 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');381382 const events: any = [];383 contract.events.allEvents((_: any, event: any) => {384 events.push(event);385 });386 387 await collection.approveTokens(alice, {Ethereum: receiver}, 100n);388 if (events.length == 0) await helper.wait.newBlocks(1);389 const event = events[0];390391 expect(event.event).to.be.equal('Approval');392 expect(event.address).to.be.equal(collectionAddress);393 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));394 expect(event.returnValues.spender).to.be.equal(receiver);395 expect(event.returnValues.value).to.be.equal('100');396 });397398 itEth('Events emitted for transferFrom()', async ({helper}) => {399 const [bob] = await helper.arrange.createAccounts([10n], donor);400 const receiver = helper.eth.createAccount();401 const collection = await helper.ft.mintCollection(alice);402 await collection.mint(alice, 200n);403 await collection.approveTokens(alice, {Substrate: bob.address}, 100n);404405 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);406 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');407408 const events: any = [];409 contract.events.allEvents((_: any, event: any) => {410 events.push(event);411 });412413 await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);414 if (events.length == 0) await helper.wait.newBlocks(1);415 let event = events[0];416417 expect(event.event).to.be.equal('Transfer');418 expect(event.address).to.be.equal(collectionAddress);419 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));420 expect(event.returnValues.to).to.be.equal(receiver);421 expect(event.returnValues.value).to.be.equal('51');422423 event = events[1];424 expect(event.event).to.be.equal('Approval');425 expect(event.address).to.be.equal(collectionAddress);426 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));427 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));428 expect(event.returnValues.value).to.be.equal('49');429 });430431 itEth('Events emitted for transfer()', async ({helper}) => {432 const receiver = helper.eth.createAccount();433 const collection = await helper.ft.mintCollection(alice);434 await collection.mint(alice, 200n);435436 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);437 const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');438439 const events: any = [];440 contract.events.allEvents((_: any, event: any) => {441 events.push(event);442 });443 444 await collection.transfer(alice, {Ethereum:receiver}, 51n);445 if (events.length == 0) await helper.wait.newBlocks(1);446 const event = events[0];447448 expect(event.event).to.be.equal('Transfer');449 expect(event.address).to.be.equal(collectionAddress);450 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));451 expect(event.returnValues.to).to.be.equal(receiver);452 expect(event.returnValues.value).to.be.equal('51');453 });454455 itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {456 const sender = await helper.eth.createAccountWithBalance(donor, 100n);457458 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);459460 const receiver = helper.eth.createAccount();461462 await collection.mint(alice, 200n, {Substrate: alice.address});463 await collection.approveTokens(alice, {Ethereum: sender}, 100n);464465 const address = helper.ethAddress.fromCollectionId(collection.collectionId);466 const contract = helper.ethNativeContract.collection(address, 'ft');467468 const from = helper.ethCrossAccount.fromKeyringPair(alice);469 const to = helper.ethCrossAccount.fromAddress(receiver);470 471 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});472473 expect(result.events).to.be.like({474 Transfer: {475 address,476 event: 'Transfer',477 returnValues: {478 from: helper.address.substrateToEth(alice.address),479 to: receiver,480 value: '51',481 },482 },483 Approval: {484 address,485 event: 'Approval',486 returnValues: {487 owner: helper.address.substrateToEth(alice.address),488 spender: sender,489 value: '49',490 },491 }});492 });493});tests/src/eth/migration.seqtest.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/migration.seqtest.ts
@@ -0,0 +1,108 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+describe('EVM Migrations', () => {
+ let superuser: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (_helper, privateKey) => {
+ superuser = await privateKey('//Alice');
+ });
+ });
+
+ // todo:playgrounds requires sudo, look into later
+ itEth('Deploy contract saved state', async ({helper}) => {
+ /*
+ contract StatefulContract {
+ uint counter;
+ mapping (uint => uint) kv;
+
+ function inc() public {
+ counter = counter + 1;
+ }
+ function counterValue() public view returns (uint) {
+ return counter;
+ }
+
+ function set(uint key, uint value) public {
+ kv[key] = value;
+ }
+
+ function get(uint key) public view returns (uint) {
+ return kv[key];
+ }
+ }
+ */
+ const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6';
+ const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033';
+ const DATA = [
+ // counter = 10
+ ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'],
+ // kv = {1: 1, 2: 2, 3: 3, 4: 4},
+ ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'],
+ ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'],
+ ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'],
+ ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'],
+ ];
+
+ const caller = await helper.eth.createAccountWithBalance(superuser);
+
+ const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]);
+ const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]);
+ const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]);
+ await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled;
+ await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled;
+ await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled;
+
+ const web3 = helper.getWeb3();
+ const contract = new web3.eth.Contract([
+ {
+ inputs: [],
+ name: 'counterValue',
+ outputs: [{
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [{
+ internalType: 'uint256',
+ name: 'key',
+ type: 'uint256',
+ }],
+ name: 'get',
+ outputs: [{
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ }],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS});
+
+ expect(await contract.methods.counterValue().call()).to.be.equal('10');
+ for (let i = 1; i <= 4; i++) {
+ expect(await contract.methods.get(i).call()).to.be.equal(i.toString());
+ }
+ });
+});
tests/src/eth/migration.test.tsdiffbeforeafterboth--- a/tests/src/eth/migration.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-import {expect, itEth, usingEthPlaygrounds} from './util';
-import {IKeyringPair} from '@polkadot/types/types';
-
-describe('EVM Migrations', () => {
- let superuser: IKeyringPair;
-
- before(async function() {
- await usingEthPlaygrounds(async (_helper, privateKey) => {
- superuser = await privateKey('//Alice');
- });
- });
-
- // todo:playgrounds requires sudo, look into later
- itEth('Deploy contract saved state', async ({helper}) => {
- /*
- contract StatefulContract {
- uint counter;
- mapping (uint => uint) kv;
-
- function inc() public {
- counter = counter + 1;
- }
- function counterValue() public view returns (uint) {
- return counter;
- }
-
- function set(uint key, uint value) public {
- kv[key] = value;
- }
-
- function get(uint key) public view returns (uint) {
- return kv[key];
- }
- }
- */
- const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6';
- const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033';
- const DATA = [
- // counter = 10
- ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'],
- // kv = {1: 1, 2: 2, 3: 3, 4: 4},
- ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'],
- ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'],
- ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'],
- ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'],
- ];
-
- const caller = await helper.eth.createAccountWithBalance(superuser);
-
- const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]);
- const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]);
- const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]);
- await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled;
- await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled;
- await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled;
-
- const web3 = helper.getWeb3();
- const contract = new web3.eth.Contract([
- {
- inputs: [],
- name: 'counterValue',
- outputs: [{
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- }],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [{
- internalType: 'uint256',
- name: 'key',
- type: 'uint256',
- }],
- name: 'get',
- outputs: [{
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- }],
- stateMutability: 'view',
- type: 'function',
- },
- ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS});
-
- expect(await contract.methods.counterValue().call()).to.be.equal('10');
- for (let i = 1; i <= 4; i++) {
- expect(await contract.methods.get(i).call()).to.be.equal(i.toString());
- }
- });
-});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -139,11 +139,13 @@
describe('NFT: Plain calls', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
+ let bob: IKeyringPair;
+ let charlie: IKeyringPair;
before(async function() {
await usingEthPlaygrounds(async (helper, privateKey) => {
donor = await privateKey({filename: __filename});
- [alice] = await helper.arrange.createAccounts([10n], donor);
+ [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
});
});
@@ -247,11 +249,10 @@
});
itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
const token = await collection.mintToken(alice, {Substrate: owner.address});
@@ -277,11 +278,10 @@
});
itEth('Can perform approveCross()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await helper.eth.createAccountWithBalance(alice, 100n);
- const receiver = await privateKey('//Charlie');
+ const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+ const receiver = charlie;
const token = await collection.mintToken(alice, {Ethereum: owner});
@@ -373,11 +373,13 @@
describe('NFT: Fees', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
+ let bob: IKeyringPair;
+ let charlie: IKeyringPair;
before(async function() {
await usingEthPlaygrounds(async (helper, privateKey) => {
donor = await privateKey({filename: __filename});
- [alice] = await helper.arrange.createAccounts([10n], donor);
+ [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
});
});
@@ -410,14 +412,14 @@
});
itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
- const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+ const collectionMinter = alice;
+ const owner = bob;
+ const receiver = charlie;
+ const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(alice, 100n);
- const receiver = await privateKey('//Charlie');
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
- const token = await collection.mintToken(alice, {Substrate: owner.address});
+ const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});
const address = helper.ethAddress.fromCollectionId(collection.collectionId);
const contract = helper.ethNativeContract.collection(address, 'nft');
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 alice: 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});
+ [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
});
});
@@ -228,11 +232,10 @@
});
itEth('Can perform burnFrom()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await helper.eth.createAccountWithBalance(alice, 100n);
- const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+ const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
const token = await collection.mintToken(alice, 100n, {Ethereum: owner});
@@ -262,11 +265,10 @@
});
itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
-
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
@@ -295,12 +297,11 @@
});
itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
- const owner = await privateKey('//Bob');
- const spender = await helper.eth.createAccountWithBalance(alice, 100n);
- const receiver = await privateKey('//Charlie');
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+ const receiver = charlie;
const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
tests/src/eth/scheduling.test.tsdiffbeforeafterboth--- a/tests/src/eth/scheduling.test.ts
+++ b/tests/src/eth/scheduling.test.ts
@@ -26,8 +26,9 @@
});
});
- itEth.ifWithPallets('Successfully schedules and periodically executes an EVM contract', [Pallets.Scheduler], async ({helper, privateKey}) => {
- const alice = await privateKey('//Alice');
+ itEth.ifWithPallets.only('Successfully schedules and periodically executes an EVM contract', [Pallets.Scheduler], async ({helper, privateKey}) => {
+ 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.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/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);
- // });
- });
-});