git.delta.rocks / unique-network / refs/commits / d8e7f8d1f6ae

difftreelog

Merge pull request #640 from UniqueNetwork/refactor/xcm-n-rmrk-tests-playgrnds

ut-akuznetsov2022-10-17parents: #16b8a7a #68bf2bb.patch.diff
in: master
Refactor/xcm n rmrk tests playgrnds

36 files changed

modified.docker/xcm-config/launch-config-xcm-unique-rococo.jsondiffbeforeafterboth

no syntactic changes

modifiedtests/src/config.tsdiffbeforeafterboth
19const config = {19const config = {
20 substrateUrl: process.env.substrateUrl || 'ws://127.0.0.1:9944',20 substrateUrl: process.env.substrateUrl || 'ws://127.0.0.1:9944',
21 frontierUrl: process.env.frontierUrl || 'http://127.0.0.1:9933',21 frontierUrl: process.env.frontierUrl || 'http://127.0.0.1:9933',
22 relayUrl: process.env.relayUrl || 'ws://127.0.0.1:9844',
23 acalaUrl: process.env.acalaUrl || 'ws://127.0.0.1:9946',
24 karuraUrl: process.env.acalaUrl || 'ws://127.0.0.1:9946',
25 moonbeamUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947',
26 moonriverUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947',
27 westmintUrl: process.env.westmintUrl || 'ws://127.0.0.1:9948',
22};28};
2329
24export default config;30export default config;
deletedtests/src/deprecated-helpers/contracthelpers.tsdiffbeforeafterboth

no changes

deletedtests/src/deprecated-helpers/eth/helpers.d.tsdiffbeforeafterboth

no changes

deletedtests/src/deprecated-helpers/eth/helpers.tsdiffbeforeafterboth

no changes

deletedtests/src/deprecated-helpers/helpers.tsdiffbeforeafterboth

no changes

deletedtests/src/deprecated-helpers/util.tsdiffbeforeafterboth

no changes

modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
361 }361 }
362362
363 clearApi() {363 clearApi() {
364 super.clearApi();
364 this.web3 = null;365 this.web3 = null;
365 }366 }
366367
modifiedtests/src/rmrk/acceptNft.test.tsdiffbeforeafterboth
7 acceptNft,7 acceptNft,
8} from './util/tx';8} from './util/tx';
9import {NftIdTuple} from './util/fetch';9import {NftIdTuple} from './util/fetch';
10import {isNftChildOfAnother, expectTxFailure} from './util/helpers';10import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers';
11import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
1211
13describe('integration test: accept NFT', () => {12describe('integration test: accept NFT', () => {
14 let api: any;13 let api: any;
104 expect(isChild).to.be.false;103 expect(isChild).to.be.false;
105 });104 });
106105
107 after(() => { api.disconnect(); });106 after(async() => { await api.disconnect(); });
108});107});
109108
modifiedtests/src/rmrk/addResource.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {expect} from 'chai';
2import {getApiConnection} from '../substrate/substrate-api';2import {getApiConnection} from '../substrate/substrate-api';
3import {NftIdTuple} from './util/fetch';3import {NftIdTuple} from './util/fetch';
4import {expectTxFailure, getResourceById} from './util/helpers';4import {expectTxFailure, getResourceById, requirePallets, Pallets} from './util/helpers';
5import {5import {
6 addNftBasicResource,6 addNftBasicResource,
7 acceptNftResource,7 acceptNftResource,
12 addNftComposableResource,12 addNftComposableResource,
13} from './util/tx';13} from './util/tx';
14import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup';14import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup';
15import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
1615
17describe('integration test: add NFT resource', () => {16describe('integration test: add NFT resource', () => {
18 const Alice = '//Alice';17 const Alice = '//Alice';
433432
434433
435 after(() => {434 after(() => {
436 api.disconnect();435 after(async() => { await api.disconnect(); });
437 });436 });
438});437});
439438
modifiedtests/src/rmrk/addTheme.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {expect} from 'chai';
2import {getApiConnection} from '../substrate/substrate-api';2import {getApiConnection} from '../substrate/substrate-api';
3import {createBase, addTheme} from './util/tx';3import {createBase, addTheme} from './util/tx';
4import {expectTxFailure} from './util/helpers';4import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
5import {getThemeNames} from './util/fetch';5import {getThemeNames} from './util/fetch';
6import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
76
8describe('integration test: add Theme to Base', () => {7describe('integration test: add Theme to Base', () => {
9 let api: any;8 let api: any;
126 await expectTxFailure(/rmrkEquip\.PermissionError/, tx);125 await expectTxFailure(/rmrkEquip\.PermissionError/, tx);
127 });126 });
128127
129 after(() => { api.disconnect(); });128 after(async() => { await api.disconnect(); });
130});129});
131130
modifiedtests/src/rmrk/burnNft.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {expectTxFailure} from './util/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {NftIdTuple, getChildren} from './util/fetch';3import {NftIdTuple, getChildren} from './util/fetch';
4import {burnNft, createCollection, sendNft, mintNft} from './util/tx';4import {burnNft, createCollection, sendNft, mintNft} from './util/tx';
55
6import chai from 'chai';6import chai from 'chai';
7import chaiAsPromised from 'chai-as-promised';7import chaiAsPromised from 'chai-as-promised';
8import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
98
10chai.use(chaiAsPromised);9chai.use(chaiAsPromised);
11const expect = chai.expect;10const expect = chai.expect;
167 });166 });
168 });167 });
169168
170 after(() => {169 after(async() => { await api.disconnect(); });
171 api.disconnect();
172 });
173});170});
modifiedtests/src/rmrk/changeCollectionIssuer.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {3import {
5 changeIssuer,4 changeIssuer,
6 createCollection,5 createCollection,
50 });49 });
51 });50 });
5251
53 after(() => {52 after(async() => { await api.disconnect(); });
54 api.disconnect();
55 });
56});53});
modifiedtests/src/rmrk/createBase.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {requirePallets, Pallets} from './util/helpers';
3import {createCollection, createBase} from './util/tx';3import {createCollection, createBase} from './util/tx';
44
5describe('integration test: create new Base', () => {5describe('integration test: create new Base', () => {
84 ]);84 ]);
85 });85 });
8686
87 after(() => { api.disconnect(); });87 after(async() => { await api.disconnect(); });
88});88});
8989
modifiedtests/src/rmrk/createCollection.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {requirePallets, Pallets} from './util/helpers';
3import {createCollection} from './util/tx';3import {createCollection} from './util/tx';
44
5describe('Integration test: create new collection', () => {5describe('Integration test: create new collection', () => {
21 await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol');21 await createCollection(api, alice, 'no-limit-metadata', null, 'no-limit-symbol');
22 });22 });
2323
24 after(() => { api.disconnect(); });24 after(async() => { await api.disconnect(); });
25});25});
2626
modifiedtests/src/rmrk/deleteCollection.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {createCollection, deleteCollection} from './util/tx';3import {createCollection, deleteCollection} from './util/tx';
54
6describe('integration test: delete collection', () => {5describe('integration test: delete collection', () => {
43 });42 });
44 });43 });
4544
46 after(() => {45 after(async() => { await api.disconnect(); });
47 api.disconnect();
48 });
49});46});
modifiedtests/src/rmrk/equipNft.test.tsdiffbeforeafterboth
1import {ApiPromise} from '@polkadot/api';1import {ApiPromise} from '@polkadot/api';
2import {expect} from 'chai';2import {expect} from 'chai';
3import {getApiConnection} from '../substrate/substrate-api';3import {getApiConnection} from '../substrate/substrate-api';
4import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
5import {getNft, getParts, NftIdTuple} from './util/fetch';4import {getNft, getParts, NftIdTuple} from './util/fetch';
6import {expectTxFailure} from './util/helpers';5import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
7import {6import {
8 addNftComposableResource,7 addNftComposableResource,
9 addNftSlotResource,8 addNftSlotResource,
337 await expectTxFailure(/rmrkEquip\.CollectionNotEquippable/, tx);336 await expectTxFailure(/rmrkEquip\.CollectionNotEquippable/, tx);
338 });337 });
339338
340 after(() => {339 after(async() => { await api.disconnect(); });
341 api.disconnect();
342 });
343});340});
modifiedtests/src/rmrk/getOwnedNfts.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {expect} from 'chai';
2import {getApiConnection} from '../substrate/substrate-api';2import {getApiConnection} from '../substrate/substrate-api';
3import {requirePallets, Pallets} from '../deprecated-helpers/helpers';3import {requirePallets, Pallets} from './util/helpers';
4import {getOwnedNfts} from './util/fetch';4import {getOwnedNfts} from './util/fetch';
5import {mintNft, createCollection} from './util/tx';5import {mintNft, createCollection} from './util/tx';
66
76 });76 });
77 });77 });
7878
79 after(() => { api.disconnect(); });79 after(async() => { await api.disconnect(); });
80});80});
8181
modifiedtests/src/rmrk/lockCollection.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {createCollection, lockCollection, mintNft} from './util/tx';3import {createCollection, lockCollection, mintNft} from './util/tx';
54
6describe('integration test: lock collection', () => {5describe('integration test: lock collection', () => {
113 });112 });
114 });113 });
115114
116 after(() => {115 after(async() => { await api.disconnect(); });
117 api.disconnect();
118 });
119});116});
modifiedtests/src/rmrk/mintNft.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {expect} from 'chai';
2import {getApiConnection} from '../substrate/substrate-api';2import {getApiConnection} from '../substrate/substrate-api';
3import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
4import {getNft} from './util/fetch';3import {getNft} from './util/fetch';
5import {expectTxFailure} from './util/helpers';4import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
6import {createCollection, mintNft} from './util/tx';5import {createCollection, mintNft} from './util/tx';
76
8describe('integration test: mint new NFT', () => {7describe('integration test: mint new NFT', () => {
208 expect(nft.isSome).to.be.false;207 expect(nft.isSome).to.be.false;
209 });208 });
210209
211 after(() => { api.disconnect(); });210 after(async() => { await api.disconnect(); });
212});211});
213212
modifiedtests/src/rmrk/rejectNft.test.tsdiffbeforeafterboth
7 rejectNft,7 rejectNft,
8} from './util/tx';8} from './util/tx';
9import {getChildren, NftIdTuple} from './util/fetch';9import {getChildren, NftIdTuple} from './util/fetch';
10import {isNftChildOfAnother, expectTxFailure} from './util/helpers';10import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers';
11import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
1211
13describe('integration test: reject NFT', () => {12describe('integration test: reject NFT', () => {
14 let api: any;13 let api: any;
91 await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx);90 await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx);
92 });91 });
9392
94 after(() => { api.disconnect(); });93 after(async() => { await api.disconnect(); });
95});94});
9695
modifiedtests/src/rmrk/removeResource.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {expect} from 'chai';
2import privateKey from '../substrate/privateKey';2import privateKey from '../substrate/privateKey';
3import {executeTransaction, getApiConnection} from '../substrate/substrate-api';3import {executeTransaction, getApiConnection} from '../substrate/substrate-api';
4import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
5import {getNft, NftIdTuple} from './util/fetch';4import {getNft, NftIdTuple} from './util/fetch';
6import {expectTxFailure} from './util/helpers';5import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
7import {6import {
8 acceptNft, acceptResourceRemoval, addNftBasicResource,7 acceptNft, acceptResourceRemoval, addNftBasicResource,
9 createBase,8 createBase,
344 await expectTxFailure(/rmrkCore\.NoPermission/, tx);343 await expectTxFailure(/rmrkCore\.NoPermission/, tx);
345 });344 });
346345
347 after(() => {346 after(async() => { await api.disconnect(); });
348 api.disconnect();
349 });
350});347});
modifiedtests/src/rmrk/rmrkIsolation.test.tsdiffbeforeafterboth
1import {expect} from 'chai';1import {executeTransaction} from '../substrate/substrate-api';
2import usingApi, {executeTransaction} from '../substrate/substrate-api';
3import {
4 createCollectionExpectSuccess,
5 createItemExpectSuccess,
6 getCreateCollectionResult,
7 getDetailedCollectionInfo,
8 getGenericResult,
9 requirePallets,
10 normalizeAccountId,
11 Pallets,
12} from '../deprecated-helpers/helpers';
13import {IKeyringPair} from '@polkadot/types/types';2import {IKeyringPair} from '@polkadot/types/types';
14import {ApiPromise} from '@polkadot/api';3import {itSub, expect, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../util/playgrounds';
15import {it} from 'mocha';4import {UniqueHelper} from '../util/playgrounds/unique';
165
17let alice: IKeyringPair;6let alice: IKeyringPair;
18let bob: IKeyringPair;7let bob: IKeyringPair;
198
20async function createRmrkCollection(api: ApiPromise, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> {9async function createRmrkCollection(helper: UniqueHelper, sender: IKeyringPair): Promise<{uniqueId: number, rmrkId: number}> {
21 const tx = api.tx.rmrkCore.createCollection('metadata', null, 'symbol');10 const result = await helper.executeExtrinsic(sender, 'api.tx.rmrkCore.createCollection', ['metadata', null, 'symbol'], true);
22 const events = await executeTransaction(api, sender, tx);
2311
24 const uniqueResult = getCreateCollectionResult(events);12 const uniqueId = helper.util.extractCollectionIdFromCreationResult(result);
13
25 const rmrkResult = getGenericResult(events, 'rmrkCore', 'CollectionCreated', (data) => {14 let rmrkId = null;
15 result.result.events.forEach(({event: {data, method, section}}) => {
16 if ((section === 'rmrkCore') && (method === 'CollectionCreated')) {
26 return parseInt(data[1].toString(), 10);17 rmrkId = parseInt(data[1].toString(), 10);
18 }
27 });19 });
2820
21 if (rmrkId === null) {
22 throw Error('No rmrkCore.CollectionCreated event was found!');
23 }
24
29 return {25 return {
30 uniqueId: uniqueResult.collectionId,26 uniqueId,
31 rmrkId: rmrkResult.data!,27 rmrkId,
32 };28 };
33}29}
3430
35async function createRmrkNft(api: ApiPromise, sender: IKeyringPair, collectionId: number): Promise<number> {31async function createRmrkNft(helper: UniqueHelper, sender: IKeyringPair, collectionId: number): Promise<number> {
36 const tx = api.tx.rmrkCore.mintNft(32 const result = await helper.executeExtrinsic(
33 sender,
34 'api.tx.rmrkCore.mintNft',
37 sender.address,35 [
36 sender.address,
38 collectionId,37 collectionId,
39 sender.address,38 sender.address,
40 null,39 null,
41 'nft-metadata',40 'nft-metadata',
41 true,
42 null,
43 ],
42 true,44 true,
43 null,
44 );45 );
46
45 const events = await executeTransaction(api, sender, tx);47 let rmrkNftId = null;
48 result.result.events.forEach(({event: {data, method, section}}) => {
46 const result = getGenericResult(events, 'rmrkCore', 'NftMinted', (data) => {49 if ((section === 'rmrkCore') && (method === 'NftMinted')) {
47 return parseInt(data[2].toString(), 10);50 rmrkNftId = parseInt(data[2].toString(), 10);
51 }
48 });52 });
4953
50 return result.data!;54 if (rmrkNftId === null) {
55 throw Error('No rmrkCore.NftMinted event was found!');
51}56 }
5257
53async function isUnique(): Promise<boolean> {58 return rmrkNftId;
54 return usingApi(async api => {
55 const chain = await api.rpc.system.chain();
56
57 return chain.eq('UNIQUE');
58 });
59}59}
6060
61describe('RMRK External Integration Test', async () => {61describe('RMRK External Integration Test', async () => {
62 const it_rmrk = (await isUnique() ? it : it.skip);
63
64 before(async function() {62 before(async function() {
65 await usingApi(async (api, privateKeyWrapper) => {63 await usingPlaygrounds(async (_helper, privateKey) => {
66 alice = privateKeyWrapper('//Alice');64 alice = privateKey('//Alice');
67 await requirePallets(this, [Pallets.RmrkCore]);
68 });65 });
69 });66 });
7067
71 it_rmrk('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', async () => {68 itSub.ifWithPallets('Creates a new RMRK collection that is mapped to a different ID and is tagged as external', [Pallets.RmrkCore], async ({helper}) => {
72 await usingApi(async api => {69 // throw away collection to bump last Unique collection ID to test ID mapping
73 // throwaway collection to bump last Unique collection ID to test ID mapping
74 await createCollectionExpectSuccess();70 await helper.nft.mintCollection(alice, {tokenPrefix: 'unqt'});
7571
76 const collectionIds = await createRmrkCollection(api, alice);72 const collectionIds = await createRmrkCollection(helper, alice);
7773
78 expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping');74 expect(collectionIds.rmrkId).to.be.lessThan(collectionIds.uniqueId, 'collection ID mapping');
7975
80 const collection = (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!;76 const collection = (await helper.nft.getCollectionObject(collectionIds.uniqueId).getData())!; // (await getDetailedCollectionInfo(api, collectionIds.uniqueId))!;
81 expect(collection.readOnly.toHuman(), 'tagged external').to.be.true;77 expect(collection.raw.readOnly, 'tagged external').to.be.true;
82 });
83 });78 });
84});79});
8580
86describe('Negative Integration Test: External Collections, Internal Ops', async () => {81describe('Negative Integration Test: External Collections, Internal Ops', async () => {
87 let uniqueCollectionId: number;82 let uniqueCollectionId: number;
88 let rmrkCollectionId: number;83 let rmrkCollectionId: number;
89 let rmrkNftId: number;84 let rmrkNftId: number;
85 let normalizedAlice: {Substrate: string};
9086
91 const it_rmrk = (await isUnique() ? it : it.skip);87 before(async function() {
88 await usingPlaygrounds(async (helper, privateKey) => {
89 alice = privateKey('//Alice');
90 bob = privateKey('//Bob');
91 normalizedAlice = {Substrate: helper.address.normalizeSubstrateToChainFormat(alice.address)};
9292
93 before(async () => {93 await requirePalletsOrSkip(this, helper, [Pallets.RmrkCore]);
94 await usingApi(async (api, privateKeyWrapper) => {
95 alice = privateKeyWrapper('//Alice');
96 bob = privateKeyWrapper('//Bob');
9794
98 const collectionIds = await createRmrkCollection(api, alice);95 const collectionIds = await createRmrkCollection(helper, alice);
99 uniqueCollectionId = collectionIds.uniqueId;96 uniqueCollectionId = collectionIds.uniqueId;
100 rmrkCollectionId = collectionIds.rmrkId;97 rmrkCollectionId = collectionIds.rmrkId;
10198
102 rmrkNftId = await createRmrkNft(api, alice, rmrkCollectionId);99 rmrkNftId = await createRmrkNft(helper, alice, rmrkCollectionId);
103 });100 });
104 });101 });
105102
106 it_rmrk('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', async () => {103 itSub.ifWithPallets('[Negative] Forbids Unique operations with an external collection, handled by dispatch_call', [Pallets.RmrkCore], async ({helper}) => {
107 await usingApi(async api => {
108 // Collection item creation104 // Collection item creation
109105
110 const txCreateItem = api.tx.unique.createItem(uniqueCollectionId, normalizeAccountId(alice), 'NFT');106 await expect(helper.nft.mintToken(alice, {collectionId: uniqueCollectionId, owner: {Substrate: alice.address}}), 'creating item')
111 await expect(executeTransaction(api, alice, txCreateItem), 'creating item')
112 .to.be.rejectedWith(/common\.CollectionIsExternal/);107 .to.be.rejectedWith(/common\.CollectionIsExternal/);
113108
114 const txCreateMultipleItems = api.tx.unique.createMultipleItems(uniqueCollectionId, normalizeAccountId(alice), [{NFT: {}}, {NFT: {}}]);109 const txCreateMultipleItems = helper.getApi().tx.unique.createMultipleItems(uniqueCollectionId, normalizedAlice, [{NFT: {}}, {NFT: {}}]);
115 await expect(executeTransaction(api, alice, txCreateMultipleItems), 'creating multiple')110 await expect(executeTransaction(helper.getApi(), alice, txCreateMultipleItems), 'creating multiple')
116 .to.be.rejectedWith(/common\.CollectionIsExternal/);111 .to.be.rejectedWith(/common\.CollectionIsExternal/);
112
113 await expect(helper.nft.mintMultipleTokens(alice, uniqueCollectionId, [{owner: {Substrate: alice.address}}]), 'creating multiple ex')
114 .to.be.rejectedWith(/common\.CollectionIsExternal/);
117115
118 const txCreateMultipleItemsEx = api.tx.unique.createMultipleItemsEx(uniqueCollectionId, {NFT: [{}]});116 // Collection properties
119 await expect(executeTransaction(api, alice, txCreateMultipleItemsEx), 'creating multiple ex')
120 .to.be.rejectedWith(/common\.CollectionIsExternal/);
121117
122 // Collection properties118 await expect(helper.nft.setProperties(alice, uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]), 'setting collection properties')
119 .to.be.rejectedWith(/common\.CollectionIsExternal/);
123120
124 const txSetCollectionProperties = api.tx.unique.setCollectionProperties(uniqueCollectionId, [{key: 'a', value: '1'}, {key: 'b'}]);121 await expect(helper.nft.deleteProperties(alice, uniqueCollectionId, ['a']), 'deleting collection properties')
125 await expect(executeTransaction(api, alice, txSetCollectionProperties), 'setting collection properties')
126 .to.be.rejectedWith(/common\.CollectionIsExternal/);122 .to.be.rejectedWith(/common\.CollectionIsExternal/);
127123
128 const txDeleteCollectionProperties = api.tx.unique.deleteCollectionProperties(uniqueCollectionId, ['a']);124 await expect(helper.nft.setTokenPropertyPermissions(alice, uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]), 'setting property permissions')
129 await expect(executeTransaction(api, alice, txDeleteCollectionProperties), 'deleting collection properties')
130 .to.be.rejectedWith(/common\.CollectionIsExternal/);125 .to.be.rejectedWith(/common\.CollectionIsExternal/);
131126
132 const txsetTokenPropertyPermissions = api.tx.unique.setTokenPropertyPermissions(uniqueCollectionId, [{key: 'a', permission: {mutable: true}}]);127 // NFT
133 await expect(executeTransaction(api, alice, txsetTokenPropertyPermissions), 'setting property permissions')
134 .to.be.rejectedWith(/common\.CollectionIsExternal/);
135128
136 // NFT129 await expect(helper.nft.burnToken(alice, uniqueCollectionId, rmrkNftId, 1n), 'burning')
130 .to.be.rejectedWith(/common\.CollectionIsExternal/);
137131
138 const txBurn = api.tx.unique.burnItem(uniqueCollectionId, rmrkNftId, 1);132 await expect(helper.nft.burnTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, 1n), 'burning-from')
139 await expect(executeTransaction(api, alice, txBurn), 'burning').to.be.rejectedWith(/common\.CollectionIsExternal/);133 .to.be.rejectedWith(/common\.CollectionIsExternal/);
140134
141 const txBurnFrom = api.tx.unique.burnFrom(uniqueCollectionId, normalizeAccountId(alice), rmrkNftId, 1);135 await expect(helper.nft.transferToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'transferring')
142 await expect(executeTransaction(api, alice, txBurnFrom), 'burning-from').to.be.rejectedWith(/common\.CollectionIsExternal/);136 .to.be.rejectedWith(/common\.CollectionIsExternal/);
143137
144 const txTransfer = api.tx.unique.transfer(normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1);138 await expect(helper.nft.approveToken(alice, uniqueCollectionId, rmrkNftId, {Substrate: bob.address}), 'approving')
145 await expect(executeTransaction(api, alice, txTransfer), 'transferring').to.be.rejectedWith(/common\.CollectionIsExternal/);139 .to.be.rejectedWith(/common\.CollectionIsExternal/);
146140
147 const txApprove = api.tx.unique.approve(normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1);141 await expect(helper.nft.transferTokenFrom(alice, uniqueCollectionId, rmrkNftId, {Substrate: alice.address}, {Substrate: bob.address}), 'transferring-from')
148 await expect(executeTransaction(api, alice, txApprove), 'approving').to.be.rejectedWith(/common\.CollectionIsExternal/);142 .to.be.rejectedWith(/common\.CollectionIsExternal/);
149143
150 const txTransferFrom = api.tx.unique.transferFrom(normalizeAccountId(alice), normalizeAccountId(bob), uniqueCollectionId, rmrkNftId, 1);144 // NFT properties
151 await expect(executeTransaction(api, alice, txTransferFrom), 'transferring-from').to.be.rejectedWith(/common\.CollectionIsExternal/);
152145
153 // NFT properties146 await expect(helper.nft.setTokenProperties(alice, uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]), 'setting token properties')
147 .to.be.rejectedWith(/common\.CollectionIsExternal/);
154148
155 const txSetTokenProperties = api.tx.unique.setTokenProperties(uniqueCollectionId, rmrkNftId, [{key: 'a', value: '2'}]);149 await expect(helper.nft.deleteTokenProperties(alice, uniqueCollectionId, rmrkNftId, ['a']))
156 await expect(executeTransaction(api, alice, txSetTokenProperties), 'setting token properties')
157 .to.be.rejectedWith(/common\.CollectionIsExternal/);
158
159 const txDeleteTokenProperties = api.tx.unique.deleteTokenProperties(uniqueCollectionId, rmrkNftId, ['a']);
160 await expect(executeTransaction(api, alice, txDeleteTokenProperties), 'deleting token properties')
161 .to.be.rejectedWith(/common\.CollectionIsExternal/);150 .to.be.rejectedWith(/common\.CollectionIsExternal/);
162 });
163 });151 });
164152
165 it_rmrk('[Negative] Forbids Unique collection operations with an external collection', async () => {153 itSub.ifWithPallets('[Negative] Forbids Unique collection operations with an external collection', [Pallets.RmrkCore], async ({helper}) => {
166 await usingApi(async api => {154 await expect(helper.nft.burn(alice, uniqueCollectionId), 'destroying collection')
167 const txDestroyCollection = api.tx.unique.destroyCollection(uniqueCollectionId);
168 await expect(executeTransaction(api, alice, txDestroyCollection), 'destroying collection')
169 .to.be.rejectedWith(/common\.CollectionIsExternal/);155 .to.be.rejectedWith(/common\.CollectionIsExternal/);
170156
171 // Allow list157 // Allow list
172158
173 const txAddAllowList = api.tx.unique.addToAllowList(uniqueCollectionId, normalizeAccountId(bob));159 await expect(helper.nft.addToAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding to allow list')
174 await expect(executeTransaction(api, alice, txAddAllowList), 'adding to allow list')
175 .to.be.rejectedWith(/common\.CollectionIsExternal/);160 .to.be.rejectedWith(/common\.CollectionIsExternal/);
176161
177 const txRemoveAllowList = api.tx.unique.removeFromAllowList(uniqueCollectionId, normalizeAccountId(bob));162 await expect(helper.nft.removeFromAllowList(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing from allowlist')
178 await expect(executeTransaction(api, alice, txRemoveAllowList), 'removing from allowlist')
179 .to.be.rejectedWith(/common\.CollectionIsExternal/);163 .to.be.rejectedWith(/common\.CollectionIsExternal/);
180164
181 // Owner / Admin / Sponsor165 // Owner / Admin / Sponsor
182166
183 const txChangeOwner = api.tx.unique.changeCollectionOwner(uniqueCollectionId, bob.address);167 await expect(helper.nft.changeOwner(alice, uniqueCollectionId, bob.address), 'changing owner')
184 await expect(executeTransaction(api, alice, txChangeOwner), 'changing owner')
185 .to.be.rejectedWith(/common\.CollectionIsExternal/);168 .to.be.rejectedWith(/common\.CollectionIsExternal/);
186169
187 const txAddAdmin = api.tx.unique.addCollectionAdmin(uniqueCollectionId, normalizeAccountId(bob));170 await expect(helper.nft.addAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'adding admin')
188 await expect(executeTransaction(api, alice, txAddAdmin), 'adding admin')
189 .to.be.rejectedWith(/common\.CollectionIsExternal/);171 .to.be.rejectedWith(/common\.CollectionIsExternal/);
190172
191 const txRemoveAdmin = api.tx.unique.removeCollectionAdmin(uniqueCollectionId, normalizeAccountId(bob));173 await expect(helper.nft.removeAdmin(alice, uniqueCollectionId, {Substrate: bob.address}), 'removing admin')
192 await expect(executeTransaction(api, alice, txRemoveAdmin), 'removing admin')
193 .to.be.rejectedWith(/common\.CollectionIsExternal/);174 .to.be.rejectedWith(/common\.CollectionIsExternal/);
194175
195 const txAddCollectionSponsor = api.tx.unique.setCollectionSponsor(uniqueCollectionId, bob.address);176 await expect(helper.nft.setSponsor(alice, uniqueCollectionId, bob.address), 'setting sponsor')
196 await expect(executeTransaction(api, alice, txAddCollectionSponsor), 'setting sponsor')
197 .to.be.rejectedWith(/common\.CollectionIsExternal/);177 .to.be.rejectedWith(/common\.CollectionIsExternal/);
198178
199 const txConfirmCollectionSponsor = api.tx.unique.confirmSponsorship(uniqueCollectionId);179 await expect(helper.nft.confirmSponsorship(alice, uniqueCollectionId), 'confirming sponsor')
200 await expect(executeTransaction(api, alice, txConfirmCollectionSponsor), 'confirming sponsor')
201 .to.be.rejectedWith(/common\.CollectionIsExternal/);180 .to.be.rejectedWith(/common\.CollectionIsExternal/);
202181
203 const txRemoveCollectionSponsor = api.tx.unique.removeCollectionSponsor(uniqueCollectionId);182 await expect(helper.nft.removeSponsor(alice, uniqueCollectionId), 'removing sponsor')
204 await expect(executeTransaction(api, alice, txRemoveCollectionSponsor), 'removing sponsor')
205 .to.be.rejectedWith(/common\.CollectionIsExternal/);183 .to.be.rejectedWith(/common\.CollectionIsExternal/);
206 184
207 // Limits / permissions / transfers185 // Limits / permissions / transfers
208186
209 const txSetTransfers = api.tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true);187 const txSetTransfers = helper.getApi().tx.unique.setTransfersEnabledFlag(uniqueCollectionId, true);
210 await expect(executeTransaction(api, alice, txSetTransfers), 'setting transfers enabled flag')188 await expect(executeTransaction(helper.getApi(), alice, txSetTransfers), 'setting transfers enabled flag')
211 .to.be.rejectedWith(/common\.CollectionIsExternal/);189 .to.be.rejectedWith(/common\.CollectionIsExternal/);
212190
213 const txSetLimits = api.tx.unique.setCollectionLimits(uniqueCollectionId, {transfersEnabled: false});191 await expect(helper.nft.setLimits(alice, uniqueCollectionId, {transfersEnabled: false}), 'setting collection limits')
214 await expect(executeTransaction(api, alice, txSetLimits), 'setting collection limits')
215 .to.be.rejectedWith(/common\.CollectionIsExternal/);192 .to.be.rejectedWith(/common\.CollectionIsExternal/);
216193
217 const txSetPermissions = api.tx.unique.setCollectionPermissions(uniqueCollectionId, {access: 'AllowList'});194 await expect(helper.nft.setPermissions(alice, uniqueCollectionId, {access: 'AllowList'}), 'setting collection permissions')
218 await expect(executeTransaction(api, alice, txSetPermissions), 'setting collection permissions')
219 .to.be.rejectedWith(/common\.CollectionIsExternal/);195 .to.be.rejectedWith(/common\.CollectionIsExternal/);
220 });
221 });196 });
222});197});
223198
224describe('Negative Integration Test: Internal Collections, External Ops', async () => {199describe('Negative Integration Test: Internal Collections, External Ops', async () => {
225 let collectionId: number;200 let collectionId: number;
226 let nftId: number;201 let nftId: number;
227202
228 const it_rmrk = (await isUnique() ? it : it.skip);
229
230 before(async () => {203 before(async () => {
231 await usingApi(async (api, privateKeyWrapper) => {204 await usingPlaygrounds(async (helper, privateKey) => {
232 alice = privateKeyWrapper('//Alice');205 alice = privateKey('//Alice');
233 bob = privateKeyWrapper('//Bob');206 bob = privateKey('//Bob');
234207
235 collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'iceo'});
236 nftId = await createItemExpectSuccess(alice, collectionId, 'NFT');209 collectionId = collection.collectionId;
210 nftId = (await collection.mintToken(alice)).tokenId;
237 });211 });
238 });212 });
239213
240 it_rmrk('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', async () => {214 itSub.ifWithPallets('[Negative] Forbids RMRK operations with the internal collection and NFT (due to the lack of mapping)', [Pallets.RmrkCore], async ({helper}) => {
241 await usingApi(async api => {215 const api = helper.getApi();
242 const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address);
243 await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer')
244 .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/);
245216
246 const maxBurns = 10;217 const txChangeOwner = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address);
218 await expect(executeTransaction(api, alice, txChangeOwner), 'changing collection issuer')
219 .to.be.rejectedWith(/rmrkCore\.CollectionUnknown/);
220
221 const maxBurns = 10;
247 const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns);222 const txBurnItem = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns);
248 await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/);223 await expect(executeTransaction(api, alice, txBurnItem), 'burning NFT').to.be.rejectedWith(/rmrkCore\.CollectionUnknown/);
249 });
250 });224 });
251});225});
252226
modifiedtests/src/rmrk/sendNft.test.tsdiffbeforeafterboth
2import {getApiConnection} from '../substrate/substrate-api';2import {getApiConnection} from '../substrate/substrate-api';
3import {createCollection, mintNft, sendNft} from './util/tx';3import {createCollection, mintNft, sendNft} from './util/tx';
4import {NftIdTuple} from './util/fetch';4import {NftIdTuple} from './util/fetch';
5import {isNftChildOfAnother, expectTxFailure} from './util/helpers';5import {isNftChildOfAnother, expectTxFailure, requirePallets, Pallets} from './util/helpers';
6import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
76
8describe('integration test: send NFT', () => {7describe('integration test: send NFT', () => {
9 let api: any;8 let api: any;
252 await expectTxFailure(/rmrkCore\.NoPermission/, tx);251 await expectTxFailure(/rmrkCore\.NoPermission/, tx);
253 });252 });
254253
255 after(() => { api.disconnect(); });254 after(async() => { await api.disconnect(); });
256});255});
257256
modifiedtests/src/rmrk/setCollectionProperty.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {createCollection, setPropertyCollection} from './util/tx';3import {createCollection, setPropertyCollection} from './util/tx';
54
6describe('integration test: set collection property', () => {5describe('integration test: set collection property', () => {
63 });62 });
64 });63 });
6564
66 after(() => {65 after(async() => { await api.disconnect(); });
67 api.disconnect();
68 });
69});66});
modifiedtests/src/rmrk/setEquippableList.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {createCollection, createBase, setEquippableList} from './util/tx';3import {createCollection, createBase, setEquippableList} from './util/tx';
54
6describe("integration test: set slot's Equippable List", () => {5describe("integration test: set slot's Equippable List", () => {
111 await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx);110 await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx);
112 });111 });
113112
114 after(() => { api.disconnect(); });113 after(async() => { await api.disconnect(); });
115});114});
116115
modifiedtests/src/rmrk/setNftProperty.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';
3import {NftIdTuple} from './util/fetch';2import {NftIdTuple} from './util/fetch';
4import {expectTxFailure} from './util/helpers';3import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
5import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx';4import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx';
65
7describe('integration test: set NFT property', () => {6describe('integration test: set NFT property', () => {
85 await expectTxFailure(/rmrkCore\.NoPermission/, tx);84 await expectTxFailure(/rmrkCore\.NoPermission/, tx);
86 });85 });
8786
88 after(() => { api.disconnect(); });87 after(async() => { await api.disconnect(); });
89});88});
9089
modifiedtests/src/rmrk/setResourcePriorities.test.tsdiffbeforeafterboth
1import {getApiConnection} from '../substrate/substrate-api';1import {getApiConnection} from '../substrate/substrate-api';
2import {requirePallets, Pallets} from '../deprecated-helpers/helpers';2import {expectTxFailure, requirePallets, Pallets} from './util/helpers';
3import {expectTxFailure} from './util/helpers';
4import {mintNft, createCollection, setResourcePriorities} from './util/tx';3import {mintNft, createCollection, setResourcePriorities} from './util/tx';
54
6describe('integration test: set NFT resource priorities', () => {5describe('integration test: set NFT resource priorities', () => {
55 await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx);54 await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx);
56 });55 });
5756
58 after(() => { api.disconnect(); });57 after(async() => { await api.disconnect(); });
59});58});
6059
modifiedtests/src/rmrk/util/helpers.tsdiffbeforeafterboth
10import {NftIdTuple, getChildren, getOwnedNfts, getCollectionProperties, getNftProperties, getResources} from './fetch';10import {NftIdTuple, getChildren, getOwnedNfts, getCollectionProperties, getNftProperties, getResources} from './fetch';
11import chaiAsPromised from 'chai-as-promised';11import chaiAsPromised from 'chai-as-promised';
12import chai from 'chai';12import chai from 'chai';
13import {getApiConnection} from '../../substrate/substrate-api';
14import {Context} from 'mocha';
1315
14chai.use(chaiAsPromised);16chai.use(chaiAsPromised);
15const expect = chai.expect;17const expect = chai.expect;
19 successData: T | null;21 successData: T | null;
20}22}
23
24export enum Pallets {
25 Inflation = 'inflation',
26 RmrkCore = 'rmrkcore',
27 RmrkEquip = 'rmrkequip',
28 ReFungible = 'refungible',
29 Fungible = 'fungible',
30 NFT = 'nonfungible',
31 Scheduler = 'scheduler',
32 AppPromotion = 'apppromotion',
33}
34
35let modulesNames: any;
36export function getModuleNames(api: ApiPromise): string[] {
37 if (typeof modulesNames === 'undefined')
38 modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());
39 return modulesNames;
40}
41
42export async function missingRequiredPallets(requiredPallets: string[]): Promise<string[]> {
43 const api = await getApiConnection();
44 const pallets = getModuleNames(api);
45 await api.disconnect();
46
47 return requiredPallets.filter(p => !pallets.includes(p));
48}
49
50export async function requirePallets(mocha: Context, requiredPallets: string[]) {
51 const missingPallets = await missingRequiredPallets(requiredPallets);
52
53 if (missingPallets.length > 0) {
54 const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`;
55 const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;
56 const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`;
57
58 console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg);
59
60 mocha.skip();
61 }
62}
2163
22export function makeNftOwner(api: ApiPromise, owner: string | NftIdTuple): NftOwner {64export function makeNftOwner(api: ApiPromise, owner: string | NftIdTuple): NftOwner {
23 const isNftSending = (typeof owner !== 'string');65 const isNftSending = (typeof owner !== 'string');
modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
7import {Context} from 'mocha';7import {Context} from 'mocha';
8import config from '../../config';8import config from '../../config';
9import '../../interfaces/augment-api-events';9import '../../interfaces/augment-api-events';
10import {ChainHelperBase} from './unique';
11import {ILogger} from './types';
10import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev';12import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper} from './unique.dev';
1113
12chai.use(chaiAsPromised);14chai.use(chaiAsPromised);
13export const expect = chai.expect;15export const expect = chai.expect;
1416
15export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) => {17async function usingPlaygroundsGeneral<T extends ChainHelperBase>(helperType: new(logger: ILogger) => T, url: string, code: (helper: T, privateKey: (seed: string) => IKeyringPair) => Promise<void>) {
16 const silentConsole = new SilentConsole();18 const silentConsole = new SilentConsole();
17 silentConsole.enable();19 silentConsole.enable();
1820
19 const helper = new DevUniqueHelper(new SilentLogger());21 const helper = new helperType(new SilentLogger());
2022
21 try {23 try {
22 await helper.connect(url);24 await helper.connect(url);
28 await helper.disconnect();30 await helper.disconnect();
29 silentConsole.disable();31 silentConsole.disable();
30 }32 }
31};33}
34
35export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) => {
36 return usingPlaygroundsGeneral<DevUniqueHelper>(DevUniqueHelper, url, code);
37};
38
39export const usingWestmintPlaygrounds = async (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
40 return usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);
41};
42
43export const usingRelayPlaygrounds = async (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
44 return usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);
45};
46
47export const usingAcalaPlaygrounds = async (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
48 return usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);
49};
50
51export const usingKaruraPlaygrounds = async (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
52 return usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);
53};
54
55export const usingMoonbeamPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
56 return usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);
57};
58
59export const usingMoonriverPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
60 return usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);
61};
3262
33export enum Pallets {63export enum Pallets {
34 Inflation = 'inflation',64 Inflation = 'inflation',
73itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});103itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});
74itSub.ifWithPallets = itSubIfWithPallet;104itSub.ifWithPallets = itSubIfWithPallet;
105
106export async function describeXcm(name: string, cb: () => any) {
107 (
108 process.env.RUN_XCM_TESTS ? describe : describe.skip
109 )(name, cb);
110}
75111
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
172 },172 },
173}173}
174
175export interface IForeignAssetMetadata {
176 name?: number | Uint8Array,
177 symbol?: string,
178 decimals?: number,
179 minimalBalance?: bigint,
180}
181
182export interface MoonbeamAssetInfo {
183 location: any,
184 metadata: {
185 name: string,
186 symbol: string,
187 decimals: number,
188 isFrozen: boolean,
189 minimalBalance: bigint,
190 },
191 existentialDeposit: bigint,
192 isSufficient: boolean,
193 unitsPerSecond: bigint,
194 numAssetsWeightHint: number,
195}
196
197export interface AcalaAssetMetadata {
198 name: string,
199 symbol: string,
200 decimals: number,
201 minimalBalance: bigint,
202}
203
204export interface DemocracyStandardAccountVote {
205 balance: bigint,
206 vote: {
207 aye: boolean,
208 conviction: number,
209 },
210}
174211
175export type TSubstrateAccount = string;212export type TSubstrateAccount = string;
176export type TEthereumAccount = string;213export type TEthereumAccount = string;
177export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';214export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';
178export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';215export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';
216export type TSiblingNetworkds = 'moonbeam' | 'moonriver' | 'acala' | 'karura' | 'westmint';
217export type TRelayNetworks = 'rococo' | 'westend';
218export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks;
179export type TSigner = IKeyringPair; // | 'string'219export type TSigner = IKeyringPair; // | 'string'
180220
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
2// SPDX-License-Identifier: Apache-2.02// SPDX-License-Identifier: Apache-2.0
33
4import {mnemonicGenerate} from '@polkadot/util-crypto';4import {mnemonicGenerate} from '@polkadot/util-crypto';
5import {UniqueHelper} from './unique';5import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique';
6import {ApiPromise, WsProvider} from '@polkadot/api';6import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
7import * as defs from '../../interfaces/definitions';7import * as defs from '../../interfaces/definitions';
8import {IKeyringPair} from '@polkadot/types/types';8import {IKeyringPair} from '@polkadot/types/types';
9import {EventRecord} from '@polkadot/types/interfaces';
9import {ICrossAccountId} from './types';10import {ICrossAccountId} from './types';
11import {FrameSystemEventRecord} from '@polkadot/types/lookup';
1012
11export class SilentLogger {13export class SilentLogger {
12 log(_msg: any, _level: any): void { }14 log(_msg: any, _level: any): void { }
108 }109 }
109}110}
111
112export class DevRelayHelper extends RelayHelper {}
113
114export class DevWestmintHelper extends WestmintHelper {
115 wait: WaitGroup;
116
117 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
118 options.helperBase = options.helperBase ?? DevWestmintHelper;
119
120 super(logger, options);
121 this.wait = new WaitGroup(this);
122 }
123}
124
125export class DevMoonbeamHelper extends MoonbeamHelper {
126 account: MoonbeamAccountGroup;
127 wait: WaitGroup;
128
129 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
130 options.helperBase = options.helperBase ?? DevMoonbeamHelper;
131
132 super(logger, options);
133 this.account = new MoonbeamAccountGroup(this);
134 this.wait = new WaitGroup(this);
135 }
136}
137
138export class DevMoonriverHelper extends DevMoonbeamHelper {}
139
140export class DevAcalaHelper extends AcalaHelper {
141 wait: WaitGroup;
142
143 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
144 options.helperBase = options.helperBase ?? DevAcalaHelper;
145
146 super(logger, options);
147 this.wait = new WaitGroup(this);
148 }
149}
150
151export class DevKaruraHelper extends DevAcalaHelper {}
110152
111class ArrangeGroup {153class ArrangeGroup {
112 helper: DevUniqueHelper;154 helper: DevUniqueHelper;
245 }287 }
246}288}
289
290class MoonbeamAccountGroup {
291 helper: MoonbeamHelper;
292
293 keyring: Keyring;
294 _alithAccount: IKeyringPair;
295 _baltatharAccount: IKeyringPair;
296 _dorothyAccount: IKeyringPair;
297
298 constructor(helper: MoonbeamHelper) {
299 this.helper = helper;
300
301 this.keyring = new Keyring({type: 'ethereum'});
302 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
303 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
304 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
305
306 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
307 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
308 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
309 }
310
311 alithAccount() {
312 return this._alithAccount;
313 }
314
315 baltatharAccount() {
316 return this._baltatharAccount;
317 }
318
319 dorothyAccount() {
320 return this._dorothyAccount;
321 }
322
323 create() {
324 return this.keyring.addFromUri(mnemonicGenerate());
325 }
326}
247327
248class WaitGroup {328class WaitGroup {
249 helper: DevUniqueHelper;329 helper: ChainHelperBase;
250330
251 constructor(helper: DevUniqueHelper) {331 constructor(helper: ChainHelperBase) {
252 this.helper = helper;332 this.helper = helper;
253 }333 }
254334
297 });377 });
298 }378 }
379
380 async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
381 // eslint-disable-next-line no-async-promise-executor
382 const promise = new Promise<EventRecord | null>(async (resolve) => {
383 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
384 const blockNumber = header.number.toHuman();
385 const blockHash = header.hash;
386 const eventIdStr = `${eventSection}.${eventMethod}`;
387 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
388
389 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
390
391 const apiAt = await this.helper.getApi().at(blockHash);
392 const eventRecords = (await apiAt.query.system.events()) as any;
393
394 const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {
395 return r.event.section == eventSection && r.event.method == eventMethod;
396 });
397
398 if (neededEvent) {
399 unsubscribe();
400 resolve(neededEvent);
401 } else if (maxBlocksToWait > 0) {
402 maxBlocksToWait--;
403 } else {
404 this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);
405
406 unsubscribe();
407 resolve(null);
408 }
409 });
410 });
411 return promise;
412 }
299}413}
300414
301class AdminGroup {415class AdminGroup {
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
11import {IKeyringPair} from '@polkadot/types/types';11import {IKeyringPair} from '@polkadot/types/types';
12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';
1313
14export class CrossAccountId implements ICrossAccountId {14export class CrossAccountId implements ICrossAccountId {
15 Substrate?: TSubstrateAccount;15 Substrate?: TSubstrateAccount;
252 isSuccess = isSuccess && amount === transfer.amount;252 isSuccess = isSuccess && amount === transfer.amount;
253 return isSuccess;253 return isSuccess;
254 }254 }
255
256 static bigIntToDecimals(number: bigint, decimals = 18) {
257 const numberStr = number.toString();
258 const dotPos = numberStr.length - decimals;
259
260 if (dotPos <= 0) {
261 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;
262 } else {
263 const intPart = numberStr.substring(0, dotPos);
264 const fractPart = numberStr.substring(dotPos);
265 return intPart + '.' + fractPart;
266 }
267 }
255}268}
256269
257class UniqueEventHelper {270class UniqueEventHelper {
308 }321 }
309}322}
310323
311class ChainHelperBase {324export class ChainHelperBase {
325 helperBase: any;
326
312 transactionStatus = UniqueUtil.transactionStatus;327 transactionStatus = UniqueUtil.transactionStatus;
313 chainLogType = UniqueUtil.chainLogType;328 chainLogType = UniqueUtil.chainLogType;
314 util: typeof UniqueUtil;329 util: typeof UniqueUtil;
315 eventHelper: typeof UniqueEventHelper;330 eventHelper: typeof UniqueEventHelper;
316 logger: ILogger;331 logger: ILogger;
317 api: ApiPromise | null;332 api: ApiPromise | null;
318 forcedNetwork: TUniqueNetworks | null;333 forcedNetwork: TNetworks | null;
319 network: TUniqueNetworks | null;334 network: TNetworks | null;
320 chainLog: IUniqueHelperLog[];335 chainLog: IUniqueHelperLog[];
321 children: ChainHelperBase[];336 children: ChainHelperBase[];
337 address: AddressGroup;
338 chain: ChainGroup;
322339
323 constructor(logger?: ILogger) {340 constructor(logger?: ILogger, helperBase?: any) {
341 this.helperBase = helperBase;
342
324 this.util = UniqueUtil;343 this.util = UniqueUtil;
325 this.eventHelper = UniqueEventHelper;344 this.eventHelper = UniqueEventHelper;
330 this.network = null;349 this.network = null;
331 this.chainLog = [];350 this.chainLog = [];
332 this.children = [];351 this.children = [];
352 this.address = new AddressGroup(this);
353 this.chain = new ChainGroup(this);
333 }354 }
334355
356 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {
357 Object.setPrototypeOf(helperCls.prototype, this);
358 const newHelper = new helperCls(this.logger, options);
359
360 newHelper.api = this.api;
361 newHelper.network = this.network;
362 newHelper.forceNetwork = this.forceNetwork;
363
364 this.children.push(newHelper);
365
366 return newHelper;
367 }
368
335 getApi(): ApiPromise {369 getApi(): ApiPromise {
336 if(this.api === null) throw Error('API not initialized');370 if(this.api === null) throw Error('API not initialized');
337 return this.api;371 return this.api;
341 this.chainLog = [];375 this.chainLog = [];
342 }376 }
343377
344 forceNetwork(value: TUniqueNetworks): void {378 forceNetwork(value: TNetworks): void {
345 this.forcedNetwork = value;379 this.forcedNetwork = value;
346 }380 }
347381
367 this.network = null;401 this.network = null;
368 }402 }
369403
370 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {404 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {
371 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;405 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;
406 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];
407
408 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;
409
372 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;410 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;
373 return 'opal';411 return 'opal';
374 }412 }
375413
376 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {414 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {
377 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});415 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});
378 await api.isReady;416 await api.isReady;
379417
384 return network;422 return network;
385 }423 }
386424
387 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{425 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{
388 api: ApiPromise;426 api: ApiPromise;
389 network: TUniqueNetworks;427 network: TNetworks;
390 }> {428 }> {
391 if(typeof network === 'undefined' || network === null) network = 'opal';429 if(typeof network === 'undefined' || network === null) network = 'opal';
392 const supportedRPC = {430 const supportedRPC = {
399 unique: {437 unique: {
400 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,438 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,
401 },439 },
440 rococo: {},
441 westend: {},
442 moonbeam: {},
443 moonriver: {},
444 acala: {},
445 karura: {},
446 westmint: {},
402 };447 };
403 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);448 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);
404 const rpc = supportedRPC[network];449 const rpc = supportedRPC[network];
590}635}
591636
592637
593class HelperGroup {638class HelperGroup<T extends ChainHelperBase> {
594 helper: UniqueHelper;639 helper: T;
595640
596 constructor(uniqueHelper: UniqueHelper) {641 constructor(uniqueHelper: T) {
597 this.helper = uniqueHelper;642 this.helper = uniqueHelper;
598 }643 }
599}644}
600645
601646
602class CollectionGroup extends HelperGroup {647class CollectionGroup extends HelperGroup<UniqueHelper> {
603 /**648 /**
604 * Get number of blocks when sponsored transaction is available.649 * Get number of blocks when sponsored transaction is available.
605 *650 *
2000}2045}
20012046
20022047
2003class ChainGroup extends HelperGroup {2048class ChainGroup extends HelperGroup<ChainHelperBase> {
2004 /**2049 /**
2005 * Get system properties of a chain2050 * Get system properties of a chain
2006 * @example getChainProperties();2051 * @example getChainProperties();
2054 }2099 }
2055}2100}
20562101
2102class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2103 /**
2104 * Get substrate address balance
2105 * @param address substrate address
2106 * @example getSubstrate("5GrwvaEF5zXb26Fz...")
2107 * @returns amount of tokens on address
2108 */
2109 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {
2110 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();
2111 }
20572112
2058class BalanceGroup extends HelperGroup {2113 /**
2114 * Transfer tokens to substrate address
2115 * @param signer keyring of signer
2116 * @param address substrate address of a recipient
2117 * @param amount amount of tokens to be transfered
2118 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);
2119 * @returns ```true``` if extrinsic success, otherwise ```false```
2120 */
2121 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
2122 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);
2123
2124 let transfer = {from: null, to: null, amount: 0n} as any;
2125 result.result.events.forEach(({event: {data, method, section}}) => {
2126 if ((section === 'balances') && (method === 'Transfer')) {
2127 transfer = {
2128 from: this.helper.address.normalizeSubstrate(data[0]),
2129 to: this.helper.address.normalizeSubstrate(data[1]),
2130 amount: BigInt(data[2]),
2131 };
2132 }
2133 });
2134 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from
2135 && this.helper.address.normalizeSubstrate(address) === transfer.to
2136 && BigInt(amount) === transfer.amount;
2137 return isSuccess;
2138 }
2139
2140 /**
2141 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved
2142 * @param address substrate address
2143 * @returns
2144 */
2145 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {
2146 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;
2147 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
2148 }
2149}
2150
2151class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2152 /**
2153 * Get ethereum address balance
2154 * @param address ethereum address
2155 * @example getEthereum("0x9F0583DbB855d...")
2156 * @returns amount of tokens on address
2157 */
2158 async getEthereum(address: TEthereumAccount): Promise<bigint> {
2159 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();
2160 }
2161
2162 /**
2163 * Transfer tokens to address
2164 * @param signer keyring of signer
2165 * @param address Ethereum address of a recipient
2166 * @param amount amount of tokens to be transfered
2167 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);
2168 * @returns ```true``` if extrinsic success, otherwise ```false```
2169 */
2170 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {
2171 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);
2172
2173 let transfer = {from: null, to: null, amount: 0n} as any;
2174 result.result.events.forEach(({event: {data, method, section}}) => {
2175 if ((section === 'balances') && (method === 'Transfer')) {
2176 transfer = {
2177 from: data[0].toString(),
2178 to: data[1].toString(),
2179 amount: BigInt(data[2]),
2180 };
2181 }
2182 });
2183 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from
2184 && address === transfer.to
2185 && BigInt(amount) === transfer.amount;
2186 return isSuccess;
2187 }
2188}
2189
2190class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2191 subBalanceGroup: SubstrateBalanceGroup<T>;
2192 ethBalanceGroup: EthereumBalanceGroup<T>;
2193
2194 constructor(helper: T) {
2195 super(helper);
2196 this.subBalanceGroup = new SubstrateBalanceGroup(helper);
2197 this.ethBalanceGroup = new EthereumBalanceGroup(helper);
2198 }
2199
2059 getCollectionCreationPrice(): bigint {2200 getCollectionCreationPrice(): bigint {
2060 return 2n * this.helper.balance.getOneTokenNominal();2201 return 2n * this.getOneTokenNominal();
2061 }2202 }
2062 /**2203 /**
2063 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2204 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).
2076 * @returns amount of tokens on address2217 * @returns amount of tokens on address
2077 */2218 */
2078 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2219 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {
2079 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2220 return this.subBalanceGroup.getSubstrate(address);
2080 }2221 }
20812222
2082 /**2223 /**
2085 * @returns2226 * @returns
2086 */2227 */
2087 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2228 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {
2088 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2229 return this.subBalanceGroup.getSubstrateFull(address);
2089 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
2090 }2230 }
20912231
2092 /**2232 /**
2096 * @returns amount of tokens on address2236 * @returns amount of tokens on address
2097 */2237 */
2098 async getEthereum(address: TEthereumAccount): Promise<bigint> {2238 async getEthereum(address: TEthereumAccount): Promise<bigint> {
2099 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2239 return this.ethBalanceGroup.getEthereum(address);
2100 }2240 }
21012241
2102 /**2242 /**
2108 * @returns ```true``` if extrinsic success, otherwise ```false```2248 * @returns ```true``` if extrinsic success, otherwise ```false```
2109 */2249 */
2110 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2250 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
2111 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);2251 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);
2112
2113 let transfer = {from: null, to: null, amount: 0n} as any;
2114 result.result.events.forEach(({event: {data, method, section}}) => {
2115 if ((section === 'balances') && (method === 'Transfer')) {
2116 transfer = {
2117 from: this.helper.address.normalizeSubstrate(data[0]),
2118 to: this.helper.address.normalizeSubstrate(data[1]),
2119 amount: BigInt(data[2]),
2120 };
2121 }
2122 });
2123 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from
2124 && this.helper.address.normalizeSubstrate(address) === transfer.to
2125 && BigInt(amount) === transfer.amount;
2126 return isSuccess;
2127 }2252 }
2128}2253}
21292254
2130
2131class AddressGroup extends HelperGroup {2255class AddressGroup extends HelperGroup<ChainHelperBase> {
2132 /**2256 /**
2133 * Normalizes the address to the specified ss58 format, by default ```42```.2257 * Normalizes the address to the specified ss58 format, by default ```42```.
2134 * @param address substrate address2258 * @param address substrate address
2170 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2294 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {
2171 return CrossAccountId.translateSubToEth(subAddress);2295 return CrossAccountId.translateSubToEth(subAddress);
2172 }2296 }
2297
2298 paraSiblingSovereignAccount(paraid: number) {
2299 // We are getting a *sibling* parachain sovereign account,
2300 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c
2301 const siblingPrefix = '0x7369626c';
2302
2303 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);
2304 const suffix = '000000000000000000000000000000000000000000000000';
2305
2306 return siblingPrefix + encodedParaId + suffix;
2307 }
2173}2308}
21742309
2175class StakingGroup extends HelperGroup {2310class StakingGroup extends HelperGroup<UniqueHelper> {
2176 /**2311 /**
2177 * Stake tokens for App Promotion2312 * Stake tokens for App Promotion
2178 * @param signer keyring of signer2313 * @param signer keyring of signer
2258 }2393 }
2259}2394}
22602395
2261class SchedulerGroup extends HelperGroup {2396class SchedulerGroup extends HelperGroup<UniqueHelper> {
2262 constructor(helper: UniqueHelper) {2397 constructor(helper: UniqueHelper) {
2263 super(helper);2398 super(helper);
2264 }2399 }
2314 }2449 }
2315}2450}
23162451
2452class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
2453 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
2454 await this.helper.executeExtrinsic(
2455 signer,
2456 'api.tx.foreignAssets.registerForeignAsset',
2457 [ownerAddress, location, metadata],
2458 true,
2459 );
2460 }
2461
2462 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
2463 await this.helper.executeExtrinsic(
2464 signer,
2465 'api.tx.foreignAssets.updateForeignAsset',
2466 [foreignAssetId, location, metadata],
2467 true,
2468 );
2469 }
2470}
2471
2472class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2473 palletName: string;
2474
2475 constructor(helper: T, palletName: string) {
2476 super(helper);
2477
2478 this.palletName = palletName;
2479 }
2480
2481 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {
2482 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);
2483 }
2484}
2485
2486class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2487 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {
2488 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
2489 }
2490
2491 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {
2492 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
2493 }
2494
2495 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {
2496 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
2497 }
2498}
2499
2500class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2501 async accounts(address: string, currencyId: any) {
2502 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
2503 return BigInt(free);
2504 }
2505}
2506
2507class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2508 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
2509 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
2510 }
2511
2512 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {
2513 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
2514 }
2515
2516 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {
2517 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
2518 }
2519
2520 async account(assetId: string | number, address: string) {
2521 const accountAsset = (
2522 await this.helper.callRpc('api.query.assets.account', [assetId, address])
2523 ).toJSON()! as any;
2524
2525 if (accountAsset !== null) {
2526 return BigInt(accountAsset['balance']);
2527 } else {
2528 return null;
2529 }
2530 }
2531}
2532
2533class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
2534 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
2535 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
2536 }
2537}
2538
2539class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
2540 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {
2541 const apiPrefix = 'api.tx.assetManager.';
2542
2543 const registerTx = this.helper.constructApiCall(
2544 apiPrefix + 'registerForeignAsset',
2545 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],
2546 );
2547
2548 const setUnitsTx = this.helper.constructApiCall(
2549 apiPrefix + 'setAssetUnitsPerSecond',
2550 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],
2551 );
2552
2553 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);
2554 const encodedProposal = batchCall?.method.toHex() || '';
2555 return encodedProposal;
2556 }
2557
2558 async assetTypeId(location: any) {
2559 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);
2560 }
2561}
2562
2563class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
2564 async notePreimage(signer: TSigner, encodedProposal: string) {
2565 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);
2566 }
2567
2568 externalProposeMajority(proposalHash: string) {
2569 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);
2570 }
2571
2572 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
2573 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
2574 }
2575
2576 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
2577 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
2578 }
2579}
2580
2581class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
2582 collective: string;
2583
2584 constructor(helper: MoonbeamHelper, collective: string) {
2585 super(helper);
2586
2587 this.collective = collective;
2588 }
2589
2590 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
2591 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
2592 }
2593
2594 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
2595 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
2596 }
2597
2598 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {
2599 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
2600 }
2601
2602 async proposalCount() {
2603 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
2604 }
2605}
2606
2607export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;
2317export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;2608export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;
23182609
2319export class UniqueHelper extends ChainHelperBase {2610export class UniqueHelper extends ChainHelperBase {
2320 helperBase: any;2611 balance: BalanceGroup<UniqueHelper>;
2321
2322 chain: ChainGroup;
2323 balance: BalanceGroup;
2324 address: AddressGroup;
2325 collection: CollectionGroup;2612 collection: CollectionGroup;
2326 nft: NFTGroup;2613 nft: NFTGroup;
2327 rft: RFTGroup;2614 rft: RFTGroup;
2328 ft: FTGroup;2615 ft: FTGroup;
2329 staking: StakingGroup;2616 staking: StakingGroup;
2330 scheduler: SchedulerGroup;2617 scheduler: SchedulerGroup;
2618 foreignAssets: ForeignAssetsGroup;
2619 xcm: XcmGroup<UniqueHelper>;
2620 xTokens: XTokensGroup<UniqueHelper>;
2621 tokens: TokensGroup<UniqueHelper>;
23312622
2332 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2623 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2333 super(logger);2624 super(logger, options.helperBase ?? UniqueHelper);
23342625
2335 this.helperBase = options.helperBase ?? UniqueHelper;
2336
2337 this.chain = new ChainGroup(this);
2338 this.balance = new BalanceGroup(this);2626 this.balance = new BalanceGroup(this);
2339 this.address = new AddressGroup(this);
2340 this.collection = new CollectionGroup(this);2627 this.collection = new CollectionGroup(this);
2341 this.nft = new NFTGroup(this);2628 this.nft = new NFTGroup(this);
2342 this.rft = new RFTGroup(this);2629 this.rft = new RFTGroup(this);
2343 this.ft = new FTGroup(this);2630 this.ft = new FTGroup(this);
2344 this.staking = new StakingGroup(this);2631 this.staking = new StakingGroup(this);
2345 this.scheduler = new SchedulerGroup(this);2632 this.scheduler = new SchedulerGroup(this);
2633 this.foreignAssets = new ForeignAssetsGroup(this);
2634 this.xcm = new XcmGroup(this, 'polkadotXcm');
2635 this.xTokens = new XTokensGroup(this);
2636 this.tokens = new TokensGroup(this);
2346 }2637 }
23472638
2348 clone(helperCls: UniqueHelperConstructor, options: {[key: string]: any} = {}) {2639 getSudo<T extends UniqueHelper>() {
2640 // eslint-disable-next-line @typescript-eslint/naming-convention
2349 Object.setPrototypeOf(helperCls.prototype, this);2641 const SudoHelperType = SudoHelper(this.helperBase);
2350 const newHelper = new helperCls(this.logger, options);2642 return this.clone(SudoHelperType) as T;
2643 }
2644}
23512645
2352 newHelper.api = this.api;2646export class XcmChainHelper extends ChainHelperBase {
2647 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
2648 const wsProvider = new WsProvider(wsEndpoint);
2649 this.api = new ApiPromise({
2650 provider: wsProvider,
2651 });
2353 newHelper.network = this.network;2652 await this.api.isReadyOrError;
2354 newHelper.forceNetwork = this.forceNetwork;2653 this.network = await UniqueHelper.detectNetwork(this.api);
2654 }
2655}
23552656
2356 this.children.push(newHelper);2657export class RelayHelper extends XcmChainHelper {
2658 xcm: XcmGroup<RelayHelper>;
23572659
2358 return newHelper;2660 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2661 super(logger, options.helperBase ?? RelayHelper);
2662
2663 this.xcm = new XcmGroup(this, 'xcmPallet');
2359 }2664 }
2665}
23602666
2361 getSudo<T extends UniqueHelper>() {2667export class WestmintHelper extends XcmChainHelper {
2668 balance: SubstrateBalanceGroup<WestmintHelper>;
2669 xcm: XcmGroup<WestmintHelper>;
2670 assets: AssetsGroup<WestmintHelper>;
2671 xTokens: XTokensGroup<WestmintHelper>;
2672
2673 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2674 super(logger, options.helperBase ?? WestmintHelper);
2675
2676 this.balance = new SubstrateBalanceGroup(this);
2677 this.xcm = new XcmGroup(this, 'polkadotXcm');
2678 this.assets = new AssetsGroup(this);
2679 this.xTokens = new XTokensGroup(this);
2680 }
2681}
2682
2683export class MoonbeamHelper extends XcmChainHelper {
2684 balance: EthereumBalanceGroup<MoonbeamHelper>;
2685 assetManager: MoonbeamAssetManagerGroup;
2686 assets: AssetsGroup<MoonbeamHelper>;
2687 xTokens: XTokensGroup<MoonbeamHelper>;
2688 democracy: MoonbeamDemocracyGroup;
2689 collective: {
2690 council: MoonbeamCollectiveGroup,
2691 techCommittee: MoonbeamCollectiveGroup,
2692 };
2693
2694 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2695 super(logger, options.helperBase ?? MoonbeamHelper);
2696
2697 this.balance = new EthereumBalanceGroup(this);
2698 this.assetManager = new MoonbeamAssetManagerGroup(this);
2699 this.assets = new AssetsGroup(this);
2700 this.xTokens = new XTokensGroup(this);
2701 this.democracy = new MoonbeamDemocracyGroup(this);
2702 this.collective = {
2703 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
2704 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
2705 };
2706 }
2707}
2708
2709export class AcalaHelper extends XcmChainHelper {
2710 balance: SubstrateBalanceGroup<AcalaHelper>;
2711 assetRegistry: AcalaAssetRegistryGroup;
2712 xTokens: XTokensGroup<AcalaHelper>;
2713 tokens: TokensGroup<AcalaHelper>;
2714
2715 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2716 super(logger, options.helperBase ?? AcalaHelper);
2717
2718 this.balance = new SubstrateBalanceGroup(this);
2719 this.assetRegistry = new AcalaAssetRegistryGroup(this);
2720 this.xTokens = new XTokensGroup(this);
2721 this.tokens = new TokensGroup(this);
2722 }
2723
2724 getSudo<T extends AcalaHelper>() {
2362 // eslint-disable-next-line @typescript-eslint/naming-convention2725 // eslint-disable-next-line @typescript-eslint/naming-convention
2363 const SudoHelperType = SudoUniqueHelper(this.helperBase);2726 const SudoHelperType = SudoHelper(this.helperBase);
2364 return this.clone(SudoHelperType) as T;2727 return this.clone(SudoHelperType) as T;
2365 }2728 }
2366}2729}
2411}2774}
24122775
2413// eslint-disable-next-line @typescript-eslint/naming-convention2776// eslint-disable-next-line @typescript-eslint/naming-convention
2414function SudoUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2777function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
2415 return class extends Base {2778 return class extends Base {
2416 constructor(...args: any[]) {2779 constructor(...args: any[]) {
2417 super(...args);2780 super(...args);
modifiedtests/src/xcm/xcmOpal.test.tsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import chai from 'chai';
18import chaiAsPromised from 'chai-as-promised';
19
20import {WsProvider} from '@polkadot/api';
21import {ApiOptions} from '@polkadot/api/types';
22import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
23import usingApi, {executeTransaction} from './../substrate/substrate-api';18import config from '../config';
24import {bigIntToDecimals, describe_xcm, getGenericResult, paraSiblingSovereignAccount, normalizeAccountId} from './../deprecated-helpers/helpers';19import {itSub, expect, describeXcm, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/playgrounds';
25import waitNewBlocks from './../substrate/wait-new-blocks';
26import getBalance from './../substrate/get-balance';
2720
28
29chai.use(chaiAsPromised);
30const expect = chai.expect;
31
32const STATEMINE_CHAIN = 1000;21const STATEMINE_CHAIN = 1000;
33const UNIQUE_CHAIN = 2095;22const UNIQUE_CHAIN = 2095;
3423
35const RELAY_PORT = '9844';24const relayUrl = config.relayUrl;
36const UNIQUE_PORT = '9944';25const westmintUrl = config.westmintUrl;
37const STATEMINE_PORT = '9948';26
38const STATEMINE_PALLET_INSTANCE = 50;27const STATEMINE_PALLET_INSTANCE = 50;
39const ASSET_ID = 100;28const ASSET_ID = 100;
40const ASSET_METADATA_DECIMALS = 18;29const ASSET_METADATA_DECIMALS = 18;
41const ASSET_METADATA_NAME = 'USDT';30const ASSET_METADATA_NAME = 'USDT';
42const ASSET_METADATA_DESCRIPTION = 'USDT';31const ASSET_METADATA_DESCRIPTION = 'USDT';
43const ASSET_METADATA_MINIMAL_BALANCE = 1;32const ASSET_METADATA_MINIMAL_BALANCE = 1n;
4433
45const WESTMINT_DECIMALS = 12;34const WESTMINT_DECIMALS = 12;
4635
49// 10,000.00 (ten thousands) USDT38// 10,000.00 (ten thousands) USDT
50const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n; 39const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
5140
52describe_xcm('[XCM] Integration test: Exchanging USDT with Westmint', () => {41describeXcm('[XCM] Integration test: Exchanging USDT with Westmint', () => {
53 let alice: IKeyringPair;42 let alice: IKeyringPair;
54 let bob: IKeyringPair;43 let bob: IKeyringPair;
55 44
6958
7059
71 before(async () => {60 before(async () => {
72 await usingApi(async (api, privateKeyWrapper) => {61 await usingPlaygrounds(async (_helper, privateKey) => {
73 alice = privateKeyWrapper('//Alice');62 alice = privateKey('//Alice');
74 bob = privateKeyWrapper('//Bob'); // funds donor63 bob = privateKey('//Bob'); // funds donor
75 });64 });
7665
77 const statemineApiOptions: ApiOptions = {66 await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
78 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
79 };
80
81 const uniqueApiOptions: ApiOptions = {
82 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
83 };
84
85 const relayApiOptions: ApiOptions = {
86 provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT),
87 };
88
89 await usingApi(async (api) => {
90
91 // 350.00 (three hundred fifty) DOT67 // 350.00 (three hundred fifty) DOT
92 const fundingAmount = 3_500_000_000_000; 68 const fundingAmount = 3_500_000_000_000n;
9369
94 const tx = api.tx.assets.create(ASSET_ID, alice.addressRaw, ASSET_METADATA_MINIMAL_BALANCE);70 await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE);
95 const events = await executeTransaction(api, alice, tx);71 await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
96 const result = getGenericResult(events);72 await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
97 expect(result.success).to.be.true;
9873
99 // set metadata
100 const tx2 = api.tx.assets.setMetadata(ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
101 const events2 = await executeTransaction(api, alice, tx2);
102 const result2 = getGenericResult(events2);
103 expect(result2.success).to.be.true;
104
105 // mint some amount of asset
106 const tx3 = api.tx.assets.mint(ASSET_ID, alice.addressRaw, ASSET_AMOUNT);
107 const events3 = await executeTransaction(api, alice, tx3);
108 const result3 = getGenericResult(events3);
109 expect(result3.success).to.be.true;
110
111 // funding parachain sovereing account (Parachain: 2095)74 // funding parachain sovereing account (Parachain: 2095)
112 const parachainSovereingAccount = await paraSiblingSovereignAccount(UNIQUE_CHAIN);75 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
113 const tx4 = api.tx.balances.transfer(parachainSovereingAccount, fundingAmount);76 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
114 const events4 = await executeTransaction(api, bob, tx4);77 });
115 const result4 = getGenericResult(events4);
116 expect(result4.success).to.be.true;
11778
118 }, statemineApiOptions);
11979
120
121 await usingApi(async (api) => {80 await usingPlaygrounds(async (helper) => {
122
123 const location = {81 const location = {
124 V1: {82 V1: {
144 decimals: ASSET_METADATA_DECIMALS,102 decimals: ASSET_METADATA_DECIMALS,
145 minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,103 minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
146 };104 };
147 //registerForeignAsset(owner, location, metadata)105 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
148 const tx = api.tx.foreignAssets.registerForeignAsset(alice.addressRaw, location, metadata);
149 const sudoTx = api.tx.sudo.sudo(tx as any);106 balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
150 const events = await executeTransaction(api, alice, sudoTx);
151 const result = getGenericResult(events);107 });
152 expect(result.success).to.be.true;
153108
154 [balanceOpalBefore] = await getBalance(api, [alice.address]);
155109
156 }, uniqueApiOptions);
157
158
159 // Providing the relay currency to the unique sender account110 // Providing the relay currency to the unique sender account
160 await usingApi(async (api) => {111 await usingRelayPlaygrounds(relayUrl, async (helper) => {
161 const destination = {112 const destination = {
162 V1: {113 V1: {
163 parents: 0,114 parents: 0,
196 };147 };
197148
198 const feeAssetItem = 0;149 const feeAssetItem = 0;
150 const weightLimit = 5_000_000_000;
199151
200 const weightLimit = {152 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
201 Limited: 5_000_000_000,
202 };
203
204 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
205 const events = await executeTransaction(api, alice, tx);153 });
206 const result = getGenericResult(events);
207 expect(result.success).to.be.true;
208 }, relayApiOptions);
209 154
210 });155 });
211156
212 it('Should connect and send USDT from Westmint to Opal', async () => {157 itSub('Should connect and send USDT from Westmint to Opal', async ({helper}) => {
213 158 await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
214 const statemineApiOptions: ApiOptions = {
215 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
216 };
217
218 const uniqueApiOptions: ApiOptions = {
219 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
220 };
221
222 await usingApi(async (api) => {
223
224 const dest = {159 const dest = {
225 V1: {160 V1: {
267 };202 };
268203
269 const feeAssetItem = 0;204 const feeAssetItem = 0;
205 const weightLimit = 5000000000;
270206
271 const weightLimit = {207 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);
272 Limited: 5000000000,208 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, weightLimit);
273 };
274209
275 [balanceStmnBefore] = await getBalance(api, [alice.address]);210 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);
276211
277 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(dest, beneficiary, assets, feeAssetItem, weightLimit);
278 const events = await executeTransaction(api, alice, tx);
279 const result = getGenericResult(events);
280 expect(result.success).to.be.true;
281
282 [balanceStmnAfter] = await getBalance(api, [alice.address]);
283
284 // common good parachain take commission in it native token212 // common good parachain take commission in it native token
285 console.log(213 console.log(
286 'Opal to Westmint transaction fees on Westmint: %s WND',214 'Opal to Westmint transaction fees on Westmint: %s WND',
287 bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS),215 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS),
288 );216 );
289 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;217 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
290218
291 }, statemineApiOptions);219 });
292220
293221
294 // ensure that asset has been delivered222 // ensure that asset has been delivered
295 await usingApi(async (api) => {223 await helper.wait.newBlocks(3);
296 await waitNewBlocks(api, 3);
297 // expext collection id will be with id 1
298 const free = (await api.query.fungible.balance(1, normalizeAccountId(alice.address))).toBigInt();
299224
300 [balanceOpalAfter] = await getBalance(api, [alice.address]);225 // expext collection id will be with id 1
226 const free = await helper.ft.getBalance(1, {Substrate: alice.address});
301227
302 // commission has not paid in USDT token228 balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
303 expect(free == TRANSFER_AMOUNT).to.be.true;
304 console.log(
305 'Opal to Westmint transaction fees on Opal: %s USDT',
306 bigIntToDecimals(TRANSFER_AMOUNT - free),
307 );
308 // ... and parachain native token
309 expect(balanceOpalAfter == balanceOpalBefore).to.be.true;
310 console.log(
311 'Opal to Westmint transaction fees on Opal: %s WND',
312 bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS),
313 );
314229
315 }, uniqueApiOptions);230 // commission has not paid in USDT token
231 expect(free == TRANSFER_AMOUNT).to.be.true;
232 console.log(
233 'Opal to Westmint transaction fees on Opal: %s USDT',
234 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free),
235 );
236 // ... and parachain native token
237 expect(balanceOpalAfter == balanceOpalBefore).to.be.true;
238 console.log(
239 'Opal to Westmint transaction fees on Opal: %s WND',
240 helper.util.bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS),
316 241 );
317 });242 });
318243
319 it('Should connect and send USDT from Unique to Statemine back', async () => {244 itSub('Should connect and send USDT from Unique to Statemine back', async ({helper}) => {
320
321 const uniqueApiOptions: ApiOptions = {
322 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
323 };
324
325 const statemineApiOptions: ApiOptions = {
326 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
327 };
328
329 await usingApi(async (api) => {
330 const destination = {245 const destination = {
331 V1: {246 V1: {
332 parents: 1,247 parents: 1,
333 interior: {X2: [248 interior: {X2: [
334 {
335 Parachain: STATEMINE_CHAIN,
336 },
337 {
338 AccountId32: {
339 network: 'Any',
340 id: alice.addressRaw,
341 },
342 },
343 ]},
344 },
345 };
346
347 const currencies: [any, bigint][] = [
348 [
349 {249 {
350 ForeignAssetId: 0,250 Parachain: STATEMINE_CHAIN,
351 },251 },
352 //10_000_000_000_000_000n,
353 TRANSFER_AMOUNT,
354 ],
355 [
356 {252 {
357 NativeAssetId: 'Parent',253 AccountId32: {
254 network: 'Any',
255 id: alice.addressRaw,
256 },
358 },257 },
359 400_000_000_000_000n,258 ]},
360 ],259 },
361 ];260 };
362261
363 const feeItem = 1;262 const currencies: [any, bigint][] = [
364 const destWeight = 500000000000;263 [
264 {
265 ForeignAssetId: 0,
266 },
267 //10_000_000_000_000_000n,
268 TRANSFER_AMOUNT,
269 ],
270 [
271 {
272 NativeAssetId: 'Parent',
273 },
274 400_000_000_000_000n,
275 ],
276 ];
365277
366 const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight);278 const feeItem = 1;
367 const events = await executeTransaction(api, alice, tx);
368 const result = getGenericResult(events);279 const destWeight = 500000000000;
369 expect(result.success).to.be.true;
370
371 // the commission has been paid in parachain native token
372 [balanceOpalFinal] = await getBalance(api, [alice.address]);
373 expect(balanceOpalAfter > balanceOpalFinal).to.be.true;
374 }, uniqueApiOptions);
375280
376 await usingApi(async (api) => {281 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, destWeight);
282
283 // the commission has been paid in parachain native token
284 balanceOpalFinal = await helper.balance.getSubstrate(alice.address);
285 expect(balanceOpalAfter > balanceOpalFinal).to.be.true;
286
287 await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
377 await waitNewBlocks(api, 3);288 await helper.wait.newBlocks(3);
378 289
379 // The USDT token never paid fees. Its amount not changed from begin value.290 // The USDT token never paid fees. Its amount not changed from begin value.
380 // Also check that xcm transfer has been succeeded 291 // Also check that xcm transfer has been succeeded
381 const free = ((await api.query.assets.account(100, alice.address)).toHuman()) as any;292 expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true;
382 expect(BigInt(free.balance.replace(/,/g, '')) == ASSET_AMOUNT).to.be.true;
383 }, statemineApiOptions);293 });
384 });294 });
385295
386 it('Should connect and send Relay token to Unique', async () => {296 itSub('Should connect and send Relay token to Unique', async ({helper}) => {
387
388 const uniqueApiOptions: ApiOptions = {
389 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
390 };
391
392 const uniqueApiOptions2: ApiOptions = {
393 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
394 };
395
396 const relayApiOptions: ApiOptions = {
397 provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT),
398 };
399
400 const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;297 const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
401298
402 await usingApi(async (api) => {299 balanceBobBefore = await helper.balance.getSubstrate(bob.address);
403 [balanceBobBefore] = await getBalance(api, [bob.address]);
404 balanceBobRelayTokenBefore = BigInt(((await api.query.tokens.accounts(bob.addressRaw, {NativeAssetId: 'Parent'})).toJSON() as any).free);300 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
405301
406 }, uniqueApiOptions);
407
408 // Providing the relay currency to the unique sender account302 // Providing the relay currency to the unique sender account
409 await usingApi(async (api) => {303 await usingRelayPlaygrounds(relayUrl, async (helper) => {
410 const destination = {304 const destination = {
411 V1: {305 V1: {
412 parents: 0,306 parents: 0,
445 };339 };
446340
447 const feeAssetItem = 0;341 const feeAssetItem = 0;
342 const weightLimit = 5_000_000_000;
448343
449 const weightLimit = {344 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, weightLimit);
450 Limited: 5_000_000_000,
451 };
452
453 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
454 const events = await executeTransaction(api, bob, tx);345 });
455 const result = getGenericResult(events);
456 expect(result.success).to.be.true;
457 }, relayApiOptions);
458 346
347 await helper.wait.newBlocks(3);
459348
460 await usingApi(async (api) => {349 balanceBobAfter = await helper.balance.getSubstrate(bob.address);
461 await waitNewBlocks(api, 3);350 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
462351
463 [balanceBobAfter] = await getBalance(api, [bob.address]);352 const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
464 balanceBobRelayTokenAfter = BigInt(((await api.query.tokens.accounts(bob.addressRaw, {NativeAssetId: 'Parent'})).toJSON() as any).free);
465 const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
466 console.log(353 console.log(
467 'Relay (Westend) to Opal transaction fees: %s OPL',354 'Relay (Westend) to Opal transaction fees: %s OPL',
468 bigIntToDecimals(balanceBobAfter - balanceBobBefore),355 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),
469 );356 );
470 console.log(357 console.log(
471 'Relay (Westend) to Opal transaction fees: %s WND',358 'Relay (Westend) to Opal transaction fees: %s WND',
472 bigIntToDecimals(wndFee, WESTMINT_DECIMALS),359 helper.util.bigIntToDecimals(wndFee, WESTMINT_DECIMALS),
473 );360 );
474 expect(balanceBobBefore == balanceBobAfter).to.be.true;361 expect(balanceBobBefore == balanceBobAfter).to.be.true;
475 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;362 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;
476 }, uniqueApiOptions2);
477
478 });363 });
479364
480 it('Should connect and send Relay token back', async () => {365 itSub('Should connect and send Relay token back', async ({helper}) => {
481 const uniqueApiOptions: ApiOptions = {366 const destination = {
482 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),367 V1: {
368 parents: 1,
369 interior: {X2: [
370 {
371 Parachain: STATEMINE_CHAIN,
372 },
373 {
374 AccountId32: {
375 network: 'Any',
376 id: bob.addressRaw,
377 },
378 },
379 ]},
380 },
483 };381 };
484382
485 await usingApi(async (api) => {383 const currencies: any = [
486 const destination = {
487 V1: {
488 parents: 1,
489 interior: {X2: [384 [
490 {385 {
491 Parachain: STATEMINE_CHAIN,386 NativeAssetId: 'Parent',
492 },
493 {
494 AccountId32: {
495 network: 'Any',
496 id: bob.addressRaw,
497 },
498 },
499 ]},
500 },387 },
501 };388 50_000_000_000_000_000n,
389 ],
390 ];
502391
503 const currencies: any = [392 const feeItem = 0;
504 [393 const destWeight = 500000000000;
505 {
506 NativeAssetId: 'Parent',
507 },
508 50_000_000_000_000_000n,
509 ],
510 ];
511394
512 const feeItem = 0;395 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, destWeight);
513 const destWeight = 500000000000;
514396
515 const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight);397 balanceBobFinal = await helper.balance.getSubstrate(bob.address);
516 const events = await executeTransaction(api, bob, tx);
517 const result = getGenericResult(events);
518 expect(result.success).to.be.true;
519
520 [balanceBobFinal] = await getBalance(api, [bob.address]);
521 console.log('Relay (Westend) to Opal transaction fees: %s OPL', balanceBobAfter - balanceBobFinal);398 console.log('Relay (Westend) to Opal transaction fees: %s OPL', balanceBobAfter - balanceBobFinal);
522
523 }, uniqueApiOptions);
524 });399 });
525
526});400});
527401
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import chai from 'chai';
18import chaiAsPromised from 'chai-as-promised';
19
20import {WsProvider, Keyring} from '@polkadot/api';
21import {ApiOptions} from '@polkadot/api/types';
22import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';
24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../deprecated-helpers/helpers';
25import {MultiLocation} from '@polkadot/types/interfaces';
26import {blake2AsHex} from '@polkadot/util-crypto';18import {blake2AsHex} from '@polkadot/util-crypto';
27import waitNewBlocks from '../substrate/wait-new-blocks';19import config from '../config';
28import getBalance from '../substrate/get-balance';
29import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';20import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';
21import {itSub, expect, describeXcm, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util/playgrounds';
3022
31chai.use(chaiAsPromised);
32const expect = chai.expect;
33
34const QUARTZ_CHAIN = 2095;23const QUARTZ_CHAIN = 2095;
35const KARURA_CHAIN = 2000;24const KARURA_CHAIN = 2000;
36const MOONRIVER_CHAIN = 2023;25const MOONRIVER_CHAIN = 2023;
3726
38const RELAY_PORT = 9844;27const relayUrl = config.relayUrl;
39const KARURA_PORT = 9946;28const karuraUrl = config.karuraUrl;
40const MOONRIVER_PORT = 9947;29const moonriverUrl = config.moonriverUrl;
4130
42const KARURA_DECIMALS = 12;31const KARURA_DECIMALS = 12;
4332
44const TRANSFER_AMOUNT = 2000000000000000000000000n;33const TRANSFER_AMOUNT = 2000000000000000000000000n;
4534
46function parachainApiOptions(port: number): ApiOptions {
47 return {
48 provider: new WsProvider('ws://127.0.0.1:' + port.toString()),
49 };
50}
51
52function karuraOptions(): ApiOptions {
53 return parachainApiOptions(KARURA_PORT);
54}
55
56function moonriverOptions(): ApiOptions {
57 return parachainApiOptions(MOONRIVER_PORT);
58}
59
60function relayOptions(): ApiOptions {
61 return parachainApiOptions(RELAY_PORT);
62}
63
64describe_xcm('[XCM] Integration test: Exchanging tokens with Karura', () => {35describeXcm('[XCM] Integration test: Exchanging tokens with Karura', () => {
65 let alice: IKeyringPair;36 let alice: IKeyringPair;
66 let randomAccount: IKeyringPair;37 let randomAccount: IKeyringPair;
6738
76 let balanceQuartzForeignTokenFinal: bigint;47 let balanceQuartzForeignTokenFinal: bigint;
7748
78 before(async () => {49 before(async () => {
79 await usingApi(async (api, privateKeyWrapper) => {50 await usingPlaygrounds(async (helper, privateKey) => {
80 const keyringSr25519 = new Keyring({type: 'sr25519'});51 alice = privateKey('//Alice');
81
82 alice = privateKeyWrapper('//Alice');
83 randomAccount = generateKeyringPair(keyringSr25519);52 [randomAccount] = await helper.arrange.createAccounts([0n], alice);
84 });53 });
8554
86 // Karura side55 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
87 await usingApi(
88 async (api) => {
89 const destination = {
90 V0: {
91 X2: [
92 'Parent',
93 {
94 Parachain: QUARTZ_CHAIN,
95 },
96 ],
97 },
98 };
99
100 const metadata = {
101 name: 'QTZ',
102 symbol: 'QTZ',
103 decimals: 18,
104 minimalBalance: 1,
105 };
106
107 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
108 const sudoTx = api.tx.sudo.sudo(tx as any);
109 const events = await submitTransactionAsync(alice, sudoTx);
110 const result = getGenericResult(events);
111 expect(result.success).to.be.true;
112
113 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);
114 const events1 = await submitTransactionAsync(alice, tx1);
115 const result1 = getGenericResult(events1);
116 expect(result1.success).to.be.true;
117
118 [balanceKaruraTokenInit] = await getBalance(api, [randomAccount.address]);
119 {
120 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;
121 balanceQuartzForeignTokenInit = BigInt(free);
122 }
123 },
124 karuraOptions(),
125 );
126
127 // Quartz side
128 await usingApi(async (api) => {
129 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);
130 const events0 = await submitTransactionAsync(alice, tx0);
131 const result0 = getGenericResult(events0);
132 expect(result0.success).to.be.true;
133
134 [balanceQuartzTokenInit] = await getBalance(api, [randomAccount.address]);
135 });
136 });
137
138 it('Should connect and send QTZ to Karura', async () => {
139
140 // Quartz side
141 await usingApi(async (api) => {
142
143 const destination = {56 const destination = {
144 V0: {57 V0: {
145 X2: [58 X2: [
146 'Parent',59 'Parent',
147 {60 {
148 Parachain: KARURA_CHAIN,61 Parachain: QUARTZ_CHAIN,
149 },62 },
150 ],63 ],
151 },64 },
152 };65 };
15366
154 const beneficiary = {67 const metadata = {
155 V0: {68 name: 'QTZ',
156 X1: {69 symbol: 'QTZ',
157 AccountId32: {
158 network: 'Any',
159 id: randomAccount.addressRaw,70 decimals: 18,
160 },71 minimalBalance: 1n,
161 },
162 },
163 };72 };
16473
165 const assets = {74 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
75 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
76 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
77 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
78 });
79
80 await usingPlaygrounds(async (helper) => {
81 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
82 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);
83 });
84 });
85
86 itSub('Should connect and send QTZ to Karura', async ({helper}) => {
87 const destination = {
166 V1: [88 V0: {
89 X2: [
90 'Parent',
167 {91 {
168 id: {92 Parachain: KARURA_CHAIN,
169 Concrete: {
170 parents: 0,
171 interior: 'Here',
172 },
173 },
174 fun: {
175 Fungible: TRANSFER_AMOUNT,
176 },
177 },93 },
178 ],94 ],
179 };95 },
96 };
18097
181 const feeAssetItem = 0;98 const beneficiary = {
99 V0: {
100 X1: {
101 AccountId32: {
102 network: 'Any',
103 id: randomAccount.addressRaw,
104 },
105 },
106 },
107 };
182108
183 const weightLimit = {109 const assets = {
184 Limited: 5000000000,110 V1: [
111 {
112 id: {
113 Concrete: {
114 parents: 0,
185 };115 interior: 'Here',
116 },
117 },
118 fun: {
119 Fungible: TRANSFER_AMOUNT,
120 },
121 },
122 ],
123 };
186124
187 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);125 const feeAssetItem = 0;
188 const events = await submitTransactionAsync(randomAccount, tx);
189 const result = getGenericResult(events);126 const weightLimit = 5000000000;
190 expect(result.success).to.be.true;
191127
192 [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccount.address]);128 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);
129 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
193130
194 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;131 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
195 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));132 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));
196 expect(qtzFees > 0n).to.be.true;133 expect(qtzFees > 0n).to.be.true;
197 });
198134
199 // Karura side135 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
200 await usingApi(
201 async (api) => {
202 await waitNewBlocks(api, 3);136 await helper.wait.newBlocks(3);
203 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;137 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
204 balanceQuartzForeignTokenMiddle = BigInt(free);138 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
205139
206 [balanceKaruraTokenMiddle] = await getBalance(api, [randomAccount.address]);140 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;
141 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
207142
208 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;143 console.log(
209 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
210
211 console.log(
212 '[Quartz -> Karura] transaction fees on Karura: %s KAR',144 '[Quartz -> Karura] transaction fees on Karura: %s KAR',
213 bigIntToDecimals(karFees, KARURA_DECIMALS),145 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),
214 );146 );
215 console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));147 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));
216 expect(karFees == 0n).to.be.true;148 expect(karFees == 0n).to.be.true;
217 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;149 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
218 },150 });
219 karuraOptions(),
220 );
221 });151 });
222152
223 it('Should connect to Karura and send QTZ back', async () => {153 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {
224
225 // Karura side154 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
226 await usingApi(
227 async (api) => {
228 const destination = {155 const destination = {
229 V1: {156 V1: {
230 parents: 1,157 parents: 1,
231 interior: {158 interior: {
232 X2: [159 X2: [
233 {Parachain: QUARTZ_CHAIN},160 {Parachain: QUARTZ_CHAIN},
234 {161 {
235 AccountId32: {162 AccountId32: {
236 network: 'Any',163 network: 'Any',
237 id: randomAccount.addressRaw,164 id: randomAccount.addressRaw,
238 },
239 },165 },
240 ],166 },
241 },167 ],
242 },168 },
243 };169 },
170 };
244171
245 const id = {172 const id = {
246 ForeignAsset: 0,173 ForeignAsset: 0,
247 };174 };
248175
249 const destWeight = 50000000;176 const destWeight = 50000000;
250177
251 const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight);178 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);
252 const events = await submitTransactionAsync(randomAccount, tx);179 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
253 const result = getGenericResult(events);180 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
254 expect(result.success).to.be.true;
255181
256 [balanceKaruraTokenFinal] = await getBalance(api, [randomAccount.address]);182 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;
257 {183 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;
258 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any;
259 balanceQuartzForeignTokenFinal = BigInt(free);
260 }
261184
262 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;185 console.log(
186 '[Karura -> Quartz] transaction fees on Karura: %s KAR',
263 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;187 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),
188 );
189 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));
264190
265 console.log(191 expect(karFees > 0).to.be.true;
266 '[Karura -> Quartz] transaction fees on Karura: %s KAR',
267 bigIntToDecimals(karFees, KARURA_DECIMALS),192 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
268 );
269 console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));193 });
270194
271 expect(karFees > 0).to.be.true;195 await helper.wait.newBlocks(3);
272 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
273 },
274 karuraOptions(),
275 );
276196
277 // Quartz side197 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
278 await usingApi(async (api) => {198 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
279 await waitNewBlocks(api, 3);199 expect(actuallyDelivered > 0).to.be.true;
280200
281 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccount.address]);201 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));
282 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
283 expect(actuallyDelivered > 0).to.be.true;
284202
285 console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));203 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
286
287 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
288 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));204 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));
289 expect(qtzFees == 0n).to.be.true;205 expect(qtzFees == 0n).to.be.true;
290 });
291 });206 });
292});207});
293208
294// These tests are relevant only when the foreign asset pallet is disabled209// These tests are relevant only when the foreign asset pallet is disabled
295describe_xcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {210describeXcm('[XCM] Integration test: Quartz rejects non-native tokens', () => {
296 let alice: IKeyringPair;211 let alice: IKeyringPair;
297212
298 before(async () => {213 before(async () => {
299 await usingApi(async (api, privateKeyWrapper) => {214 await usingPlaygrounds(async (_helper, privateKey) => {
300 alice = privateKeyWrapper('//Alice');215 alice = privateKey('//Alice');
301 });216 });
302 });217 });
303218
304 it('Quartz rejects tokens from the Relay', async () => {219 itSub('Quartz rejects tokens from the Relay', async ({helper}) => {
305 await usingApi(async (api) => {220 await usingRelayPlaygrounds(relayUrl, async (helper) => {
306 const destination = {221 const destination = {
307 V1: {222 V1: {
308 parents: 0,223 parents: 0,
341 };256 };
342257
343 const feeAssetItem = 0;258 const feeAssetItem = 0;
259 const weightLimit = 5_000_000_000;
344260
345 const weightLimit = {261 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
346 Limited: 5_000_000_000,
347 };262 });
348263
349 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);264 const maxWaitBlocks = 3;
350 const events = await submitTransactionAsync(alice, tx);
351 const result = getGenericResult(events);
352 expect(result.success).to.be.true;
353 }, relayOptions());
354265
355 await usingApi(async api => {266 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');
356 const maxWaitBlocks = 3;
357 const dmpQueueExecutedDownward = await waitEvent(
358 api,
359 maxWaitBlocks,
360 'dmpQueue',
361 'ExecutedDownward',
362 );
363267
364 expect(268 expect(
365 dmpQueueExecutedDownward != null,269 dmpQueueExecutedDownward != null,
366 '[Relay] dmpQueue.ExecutedDownward event is expected',270 '[Relay] dmpQueue.ExecutedDownward event is expected',
367 ).to.be.true;271 ).to.be.true;
368272
369 const event = dmpQueueExecutedDownward!.event;273 const event = dmpQueueExecutedDownward!.event;
370 const outcome = event.data[1] as XcmV2TraitsOutcome;274 const outcome = event.data[1] as XcmV2TraitsOutcome;
371275
372 expect(276 expect(
373 outcome.isIncomplete,277 outcome.isIncomplete,
374 '[Relay] The outcome of the XCM should be `Incomplete`',278 '[Relay] The outcome of the XCM should be `Incomplete`',
375 ).to.be.true;279 ).to.be.true;
376280
377 const incomplete = outcome.asIncomplete;281 const incomplete = outcome.asIncomplete;
378 expect(282 expect(
379 incomplete[1].toString() == 'AssetNotFound',283 incomplete[1].toString() == 'AssetNotFound',
380 '[Relay] The XCM error should be `AssetNotFound`',284 '[Relay] The XCM error should be `AssetNotFound`',
381 ).to.be.true;285 ).to.be.true;
382 });
383 });286 });
384287
385 it('Quartz rejects KAR tokens from Karura', async () => {288 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {
386 await usingApi(async (api) => {289 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
387 const destination = {290 const destination = {
388 V1: {291 V1: {
389 parents: 1,292 parents: 1,
407310
408 const destWeight = 50000000;311 const destWeight = 50000000;
409312
410 const tx = api.tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight);313 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);
411 const events = await submitTransactionAsync(alice, tx);314 });
412 const result = getGenericResult(events);
413 expect(result.success).to.be.true;
414 }, karuraOptions());
415315
416 await usingApi(async api => {316 const maxWaitBlocks = 3;
417 const maxWaitBlocks = 3;
418 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');
419317
420 expect(318 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');
421 xcmpQueueFailEvent != null,
422 '[Karura] xcmpQueue.FailEvent event is expected',
423 ).to.be.true;
424319
425 const event = xcmpQueueFailEvent!.event;320 expect(
321 xcmpQueueFailEvent != null,
426 const outcome = event.data[1] as XcmV2TraitsError;322 '[Karura] xcmpQueue.FailEvent event is expected',
323 ).to.be.true;
427324
428 expect(325 const event = xcmpQueueFailEvent!.event;
326 const outcome = event.data[1] as XcmV2TraitsError;
327
328 expect(
429 outcome.isUntrustedReserveLocation,329 outcome.isUntrustedReserveLocation,
430 '[Karura] The XCM error should be `UntrustedReserveLocation`',330 '[Karura] The XCM error should be `UntrustedReserveLocation`',
431 ).to.be.true;331 ).to.be.true;
432 });
433 });332 });
434});333});
435334
436describe_xcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {335describeXcm('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {
437336
438 // Quartz constants337 // Quartz constants
439 let quartzAlice: IKeyringPair;338 let quartzDonor: IKeyringPair;
440 let quartzAssetLocation;339 let quartzAssetLocation;
441340
442 let randomAccountQuartz: IKeyringPair;341 let randomAccountQuartz: IKeyringPair;
445 // Moonriver constants344 // Moonriver constants
446 let assetId: string;345 let assetId: string;
447346
448 const moonriverKeyring = new Keyring({type: 'ethereum'});
449 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
450 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
451 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
452
453 const alithAccount = moonriverKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
454 const baltatharAccount = moonriverKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
455 const dorothyAccount = moonriverKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
456
457 const councilVotingThreshold = 2;347 const councilVotingThreshold = 2;
458 const technicalCommitteeThreshold = 2;348 const technicalCommitteeThreshold = 2;
459 const votingPeriod = 3;349 const votingPeriod = 3;
464 symbol: 'xcQTZ',354 symbol: 'xcQTZ',
465 decimals: 18,355 decimals: 18,
466 isFrozen: false,356 isFrozen: false,
467 minimalBalance: 1,357 minimalBalance: 1n,
468 };358 };
469359
470 let balanceQuartzTokenInit: bigint;360 let balanceQuartzTokenInit: bigint;
478 let balanceMovrTokenFinal: bigint;368 let balanceMovrTokenFinal: bigint;
479369
480 before(async () => {370 before(async () => {
481 await usingApi(async (api, privateKeyWrapper) => {371 await usingPlaygrounds(async (helper, privateKey) => {
482 const keyringEth = new Keyring({type: 'ethereum'});372 quartzDonor = privateKey('//Alice');
483 const keyringSr25519 = new Keyring({type: 'sr25519'});373 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);
484374
485 quartzAlice = privateKeyWrapper('//Alice');
486 randomAccountQuartz = generateKeyringPair(keyringSr25519);
487 randomAccountMoonriver = generateKeyringPair(keyringEth);
488
489 balanceForeignQtzTokenInit = 0n;375 balanceForeignQtzTokenInit = 0n;
490 });376 });
491377
492 await usingApi(378 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
493 async (api) => {379 const alithAccount = helper.account.alithAccount();
380 const baltatharAccount = helper.account.baltatharAccount();
381 const dorothyAccount = helper.account.dorothyAccount();
494382
495 // >>> Sponsoring Dorothy >>>383 randomAccountMoonriver = helper.account.create();
496 console.log('Sponsoring Dorothy.......');
497 const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);
498 const events0 = await submitTransactionAsync(alithAccount, tx0);
499 const result0 = getGenericResult(events0);
500 expect(result0.success).to.be.true;
501 console.log('Sponsoring Dorothy.......DONE');
502 // <<< Sponsoring Dorothy <<<
503384
504 const sourceLocation: MultiLocation = api.createType(385 // >>> Sponsoring Dorothy >>>
505 'MultiLocation',386 console.log('Sponsoring Dorothy.......');
506 {387 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
507 parents: 1,388 console.log('Sponsoring Dorothy.......DONE');
508 interior: {X1: {Parachain: QUARTZ_CHAIN}},389 // <<< Sponsoring Dorothy <<<
509 },
510 );
511390
512 quartzAssetLocation = {XCM: sourceLocation};391 quartzAssetLocation = {
392 XCM: {
393 parents: 1,
394 interior: {X1: {Parachain: QUARTZ_CHAIN}},
513 const existentialDeposit = 1;395 },
396 };
397 const existentialDeposit = 1n;
514 const isSufficient = true;398 const isSufficient = true;
515 const unitsPerSecond = '1';399 const unitsPerSecond = 1n;
516 const numAssetsWeightHint = 0;400 const numAssetsWeightHint = 0;
517401
518 const registerTx = api.tx.assetManager.registerForeignAsset(402 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
519 quartzAssetLocation,403 location: quartzAssetLocation,
520 quartzAssetMetadata,404 metadata: quartzAssetMetadata,
521 existentialDeposit,405 existentialDeposit,
522 isSufficient,406 isSufficient,
523 );407 unitsPerSecond,
524 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');408 numAssetsWeightHint,
409 });
410 const proposalHash = blake2AsHex(encodedProposal);
525411
526 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(412 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
527 quartzAssetLocation,
528 unitsPerSecond,
529 numAssetsWeightHint,413 console.log('Encoded length %d', encodedProposal.length);
530 );
531 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');414 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
532415
533 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);416 // >>> Note motion preimage >>>
417 console.log('Note motion preimage.......');
418 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
534 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');419 console.log('Note motion preimage.......DONE');
420 // <<< Note motion preimage <<<
535421
536 // >>> Note motion preimage >>>422 // >>> Propose external motion through council >>>
537 console.log('Note motion preimage.......');423 console.log('Propose external motion through council.......');
538 const encodedProposal = batchCall?.method.toHex() || '';424 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);
539 const proposalHash = blake2AsHex(encodedProposal);425 const encodedMotion = externalMotion?.method.toHex() || '';
540 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);426 const motionHash = blake2AsHex(encodedMotion);
541 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);427 console.log('Motion hash is %s', motionHash);
542 console.log('Encoded length %d', encodedProposal.length);
543428
544 const tx1 = api.tx.democracy.notePreimage(encodedProposal);429 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);
545 const events1 = await submitTransactionAsync(baltatharAccount, tx1);
546 const result1 = getGenericResult(events1);
547 expect(result1.success).to.be.true;
548 console.log('Note motion preimage.......DONE');
549 // <<< Note motion preimage <<<
550430
551 // >>> Propose external motion through council >>>431 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;
552 console.log('Propose external motion through council.......');
553 const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);
554 const tx2 = api.tx.councilCollective.propose(432 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
555 councilVotingThreshold,
556 externalMotion,
557 externalMotion.encodedLength,
558 );
559 const events2 = await submitTransactionAsync(baltatharAccount, tx2);433 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
560 const result2 = getGenericResult(events2);
561 expect(result2.success).to.be.true;
562434
563 const encodedMotion = externalMotion?.method.toHex() || '';435 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);
564 const motionHash = blake2AsHex(encodedMotion);436 console.log('Propose external motion through council.......DONE');
565 console.log('Motion hash is %s', motionHash);437 // <<< Propose external motion through council <<<
566438
567 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);439 // >>> Fast track proposal through technical committee >>>
568 {
569 const events3 = await submitTransactionAsync(dorothyAccount, tx3);440 console.log('Fast track proposal through technical committee.......');
570 const result3 = getGenericResult(events3);441 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
571 expect(result3.success).to.be.true;
572 }442 const encodedFastTrack = fastTrack?.method.toHex() || '';
573 {
574 const events3 = await submitTransactionAsync(baltatharAccount, tx3);
575 const result3 = getGenericResult(events3);443 const fastTrackHash = blake2AsHex(encodedFastTrack);
576 expect(result3.success).to.be.true;444 console.log('FastTrack hash is %s', fastTrackHash);
577 }
578445
579 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);446 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
580 const events4 = await submitTransactionAsync(dorothyAccount, tx4);
581 const result4 = getGenericResult(events4);
582 expect(result4.success).to.be.true;
583 console.log('Propose external motion through council.......DONE');
584 // <<< Propose external motion through council <<<
585447
586 // >>> Fast track proposal through technical committee >>>448 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;
587 console.log('Fast track proposal through technical committee.......');
588 const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
589 const tx5 = api.tx.techCommitteeCollective.propose(449 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
590 technicalCommitteeThreshold,
591 fastTrack,
592 fastTrack.encodedLength,
593 );
594 const events5 = await submitTransactionAsync(alithAccount, tx5);450 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
595 const result5 = getGenericResult(events5);
596 expect(result5.success).to.be.true;
597451
598 const encodedFastTrack = fastTrack?.method.toHex() || '';452 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);
599 const fastTrackHash = blake2AsHex(encodedFastTrack);453 console.log('Fast track proposal through technical committee.......DONE');
600 console.log('FastTrack hash is %s', fastTrackHash);454 // <<< Fast track proposal through technical committee <<<
601455
602 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;456 // >>> Referendum voting >>>
603 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);457 console.log('Referendum voting.......');
604 {458 await helper.democracy.referendumVote(dorothyAccount, 0, {
605 const events6 = await submitTransactionAsync(baltatharAccount, tx6);
606 const result6 = getGenericResult(events6);
607 expect(result6.success).to.be.true;459 balance: 10_000_000_000_000_000_000n,
608 }460 vote: {aye: true, conviction: 1},
609 {
610 const events6 = await submitTransactionAsync(alithAccount, tx6);
611 const result6 = getGenericResult(events6);461 });
612 expect(result6.success).to.be.true;462 console.log('Referendum voting.......DONE');
613 }463 // <<< Referendum voting <<<
614464
615 const tx7 = api.tx.techCommitteeCollective465 // >>> Acquire Quartz AssetId Info on Moonriver >>>
616 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);
617 const events7 = await submitTransactionAsync(baltatharAccount, tx7);466 console.log('Acquire Quartz AssetId Info on Moonriver.......');
618 const result7 = getGenericResult(events7);
619 expect(result7.success).to.be.true;
620 console.log('Fast track proposal through technical committee.......DONE');
621 // <<< Fast track proposal through technical committee <<<
622467
623 // >>> Referendum voting >>>468 // Wait for the democracy execute
624 console.log('Referendum voting.......');
625 const tx8 = api.tx.democracy.vote(
626 0,
627 {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},469 await helper.wait.newBlocks(5);
628 );
629 const events8 = await submitTransactionAsync(dorothyAccount, tx8);
630 const result8 = getGenericResult(events8);
631 expect(result8.success).to.be.true;
632 console.log('Referendum voting.......DONE');
633 // <<< Referendum voting <<<
634470
635 // >>> Acquire Quartz AssetId Info on Moonriver >>>471 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();
636 console.log('Acquire Quartz AssetId Info on Moonriver.......');
637472
638 // Wait for the democracy execute473 console.log('QTZ asset ID is %s', assetId);
474 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
639 await waitNewBlocks(api, 5);475 // >>> Acquire Quartz AssetId Info on Moonriver >>>
640476
641 assetId = (await api.query.assetManager.assetTypeId({477 // >>> Sponsoring random Account >>>
478 console.log('Sponsoring random Account.......');
642 XCM: sourceLocation,479 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
643 })).toString();480 console.log('Sponsoring random Account.......DONE');
481 // <<< Sponsoring random Account <<<
644482
645 console.log('QTZ asset ID is %s', assetId);483 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
646 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
647 // >>> Acquire Quartz AssetId Info on Moonriver >>>484 });
648485
649 // >>> Sponsoring random Account >>>486 await usingPlaygrounds(async (helper) => {
650 console.log('Sponsoring random Account.......');
651 const tx10 = api.tx.balances.transfer(randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
652 const events10 = await submitTransactionAsync(baltatharAccount, tx10);
653 const result10 = getGenericResult(events10);
654 expect(result10.success).to.be.true;
655 console.log('Sponsoring random Account.......DONE');
656 // <<< Sponsoring random Account <<<
657
658 [balanceMovrTokenInit] = await getBalance(api, [randomAccountMoonriver.address]);
659 },
660 moonriverOptions(),
661 );
662
663 await usingApi(async (api) => {
664 const tx0 = api.tx.balances.transfer(randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);487 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
665 const events0 = await submitTransactionAsync(quartzAlice, tx0);488 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
666 const result0 = getGenericResult(events0);
667 expect(result0.success).to.be.true;
668
669 [balanceQuartzTokenInit] = await getBalance(api, [randomAccountQuartz.address]);
670 });489 });
671 });490 });
672491
673 it('Should connect and send QTZ to Moonriver', async () => {492 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {
674 await usingApi(async (api) => {
675 const currencyId = {493 const currencyId = {
676 NativeAssetId: 'Here',494 NativeAssetId: 'Here',
677 };495 };
678 const dest = {496 const dest = {
679 V1: {497 V1: {
680 parents: 1,498 parents: 1,
681 interior: {499 interior: {
682 X2: [500 X2: [
683 {Parachain: MOONRIVER_CHAIN},501 {Parachain: MOONRIVER_CHAIN},
684 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},502 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},
685 ],503 ],
686 },
687 },504 },
688 };505 },
689 const amount = TRANSFER_AMOUNT;506 };
507 const amount = TRANSFER_AMOUNT;
690 const destWeight = 850000000;508 const destWeight = 850000000;
691509
692 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);510 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);
693 const events = await submitTransactionAsync(randomAccountQuartz, tx);
694 const result = getGenericResult(events);
695 expect(result.success).to.be.true;
696511
697 [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccountQuartz.address]);512 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);
698 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;513 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;
699514
700 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;515 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
701 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees));516 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));
702 expect(transactionFees > 0).to.be.true;517 expect(transactionFees > 0).to.be.true;
703 });
704518
705 await usingApi(519 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
706 async (api) => {
707 await waitNewBlocks(api, 3);520 await helper.wait.newBlocks(3);
708521
709 [balanceMovrTokenMiddle] = await getBalance(api, [randomAccountMoonriver.address]);522 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);
710523
711 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;524 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;
712 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees));525 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));
713 expect(movrFees == 0n).to.be.true;526 expect(movrFees == 0n).to.be.true;
714527
715 const qtzRandomAccountAsset = (528 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);
716 await api.query.assets.account(assetId, randomAccountMoonriver.address)
717 ).toJSON()! as any;
718
719 balanceForeignQtzTokenMiddle = BigInt(qtzRandomAccountAsset['balance']);
720 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;529 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;
721 console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));530 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));
722 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;531 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
723 },532 });
724 moonriverOptions(),
725 );
726 });533 });
727534
728 it('Should connect to Moonriver and send QTZ back', async () => {535 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {
729 await usingApi(536 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
730 async (api) => {
731 const asset = {537 const asset = {
732 V1: {538 V1: {
733 id: {539 id: {
734 Concrete: {540 Concrete: {
735 parents: 1,541 parents: 1,
736 interior: {542 interior: {
737 X1: {Parachain: QUARTZ_CHAIN},543 X1: {Parachain: QUARTZ_CHAIN},
738 },
739 },544 },
740 },545 },
741 fun: {
742 Fungible: TRANSFER_AMOUNT,
743 },
744 },546 },
745 };547 fun: {
746 const destination = {
747 V1: {
748 parents: 1,548 Fungible: TRANSFER_AMOUNT,
749 interior: {
750 X2: [
751 {Parachain: QUARTZ_CHAIN},
752 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},
753 ],
754 },
755 },549 },
756 };550 },
551 };
757 const destWeight = 50000000;552 const destination = {
553 V1: {
554 parents: 1,
555 interior: {
556 X2: [
557 {Parachain: QUARTZ_CHAIN},
558 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},
559 ],
560 },
561 },
562 };
563 const destWeight = 50000000;
758564
759 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);565 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);
760 const events = await submitTransactionAsync(randomAccountMoonriver, tx);
761 const result = getGenericResult(events);
762 expect(result.success).to.be.true;
763566
764 [balanceMovrTokenFinal] = await getBalance(api, [randomAccountMoonriver.address]);567 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);
765568
766 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;569 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;
767 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees));570 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));
768 expect(movrFees > 0).to.be.true;571 expect(movrFees > 0).to.be.true;
769572
770 const qtzRandomAccountAsset = (573 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);
771 await api.query.assets.account(assetId, randomAccountMoonriver.address)
772 ).toJSON()! as any;
773574
774 expect(qtzRandomAccountAsset).to.be.null;575 expect(qtzRandomAccountAsset).to.be.null;
775576
776 balanceForeignQtzTokenFinal = 0n;577 balanceForeignQtzTokenFinal = 0n;
777578
778 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;579 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;
779 console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));580 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));
780 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;581 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
781 },582 });
782 moonriverOptions(),
783 );
784583
785 await usingApi(async (api) => {584 await helper.wait.newBlocks(3);
786 await waitNewBlocks(api, 3);
787585
788 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccountQuartz.address]);586 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);
789 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;587 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
790 expect(actuallyDelivered > 0).to.be.true;588 expect(actuallyDelivered > 0).to.be.true;
791589
792 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));590 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));
793591
794 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;592 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
795 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));593 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));
796 expect(qtzFees == 0n).to.be.true;594 expect(qtzFees == 0n).to.be.true;
797 });
798 });595 });
799});596});
800597
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import chai from 'chai';
18import chaiAsPromised from 'chai-as-promised';
19
20import {WsProvider, Keyring} from '@polkadot/api';
21import {ApiOptions} from '@polkadot/api/types';
22import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';
24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../deprecated-helpers/helpers';
25import {MultiLocation} from '@polkadot/types/interfaces';
26import {blake2AsHex} from '@polkadot/util-crypto';18import {blake2AsHex} from '@polkadot/util-crypto';
27import waitNewBlocks from '../substrate/wait-new-blocks';19import config from '../config';
28import getBalance from '../substrate/get-balance';
29import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';20import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';
21import {itSub, expect, describeXcm, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds} from '../util/playgrounds';
3022
31chai.use(chaiAsPromised);
32const expect = chai.expect;
33
34const UNIQUE_CHAIN = 2037;23const UNIQUE_CHAIN = 2037;
35const ACALA_CHAIN = 2000;24const ACALA_CHAIN = 2000;
36const MOONBEAM_CHAIN = 2004;25const MOONBEAM_CHAIN = 2004;
3726
38const RELAY_PORT = 9844;27const relayUrl = config.relayUrl;
39const ACALA_PORT = 9946;28const acalaUrl = config.acalaUrl;
40const MOONBEAM_PORT = 9947;29const moonbeamUrl = config.moonbeamUrl;
4130
42const ACALA_DECIMALS = 12;31const ACALA_DECIMALS = 12;
4332
44const TRANSFER_AMOUNT = 2000000000000000000000000n;33const TRANSFER_AMOUNT = 2000000000000000000000000n;
4534
46function parachainApiOptions(port: number): ApiOptions {
47 return {
48 provider: new WsProvider('ws://127.0.0.1:' + port.toString()),
49 };
50}
51
52function acalaOptions(): ApiOptions {
53 return parachainApiOptions(ACALA_PORT);
54}
55
56function moonbeamOptions(): ApiOptions {
57 return parachainApiOptions(MOONBEAM_PORT);
58}
59
60function relayOptions(): ApiOptions {
61 return parachainApiOptions(RELAY_PORT);
62}
63
64describe_xcm('[XCM] Integration test: Exchanging tokens with Acala', () => {35describeXcm('[XCM] Integration test: Exchanging tokens with Acala', () => {
65 let alice: IKeyringPair;36 let alice: IKeyringPair;
66 let randomAccount: IKeyringPair;37 let randomAccount: IKeyringPair;
6738
76 let balanceUniqueForeignTokenFinal: bigint;47 let balanceUniqueForeignTokenFinal: bigint;
7748
78 before(async () => {49 before(async () => {
79 await usingApi(async (api, privateKeyWrapper) => {50 await usingPlaygrounds(async (helper, privateKey) => {
80 const keyringSr25519 = new Keyring({type: 'sr25519'});51 alice = privateKey('//Alice');
81
82 alice = privateKeyWrapper('//Alice');
83 randomAccount = generateKeyringPair(keyringSr25519);52 [randomAccount] = await helper.arrange.createAccounts([0n], alice);
84 });53 });
8554
86 // Acala side55 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
87 await usingApi(
88 async (api) => {
89 const destination = {
90 V0: {
91 X2: [
92 'Parent',
93 {
94 Parachain: UNIQUE_CHAIN,
95 },
96 ],
97 },
98 };
99
100 const metadata = {
101 name: 'UNQ',
102 symbol: 'UNQ',
103 decimals: 18,
104 minimalBalance: 1,
105 };
106
107 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
108 const sudoTx = api.tx.sudo.sudo(tx as any);
109 const events = await submitTransactionAsync(alice, sudoTx);
110 const result = getGenericResult(events);
111 expect(result.success).to.be.true;
112
113 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);
114 const events1 = await submitTransactionAsync(alice, tx1);
115 const result1 = getGenericResult(events1);
116 expect(result1.success).to.be.true;
117
118 [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]);
119 {
120 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;
121 balanceUniqueForeignTokenInit = BigInt(free);
122 }
123 },
124 acalaOptions(),
125 );
126
127 // Unique side
128 await usingApi(async (api) => {
129 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);
130 const events0 = await submitTransactionAsync(alice, tx0);
131 const result0 = getGenericResult(events0);
132 expect(result0.success).to.be.true;
133
134 [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]);
135 });
136 });
137
138 it('Should connect and send UNQ to Acala', async () => {
139
140 // Unique side
141 await usingApi(async (api) => {
142
143 const destination = {56 const destination = {
144 V0: {57 V0: {
145 X2: [58 X2: [
146 'Parent',59 'Parent',
147 {60 {
148 Parachain: ACALA_CHAIN,61 Parachain: UNIQUE_CHAIN,
149 },62 },
150 ],63 ],
151 },64 },
152 };65 };
15366
154 const beneficiary = {67 const metadata = {
155 V0: {68 name: 'UNQ',
156 X1: {69 symbol: 'UNQ',
157 AccountId32: {
158 network: 'Any',
159 id: randomAccount.addressRaw,70 decimals: 18,
160 },71 minimalBalance: 1n,
161 },
162 },
163 };72 };
16473
165 const assets = {74 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
75 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
76 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);
77 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
78 });
79
80 await usingPlaygrounds(async (helper) => {
81 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
82 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
83 });
84 });
85
86 itSub('Should connect and send UNQ to Acala', async ({helper}) => {
87
88 const destination = {
166 V1: [89 V0: {
90 X2: [
91 'Parent',
167 {92 {
168 id: {93 Parachain: ACALA_CHAIN,
169 Concrete: {
170 parents: 0,
171 interior: 'Here',
172 },
173 },
174 fun: {
175 Fungible: TRANSFER_AMOUNT,
176 },
177 },94 },
178 ],95 ],
179 };96 },
97 };
18098
181 const feeAssetItem = 0;99 const beneficiary = {
100 V0: {
101 X1: {
102 AccountId32: {
103 network: 'Any',
104 id: randomAccount.addressRaw,
105 },
106 },
107 },
108 };
182109
183 const weightLimit = {110 const assets = {
184 Limited: 5000000000,111 V1: [
112 {
113 id: {
114 Concrete: {
115 parents: 0,
185 };116 interior: 'Here',
117 },
118 },
119 fun: {
120 Fungible: TRANSFER_AMOUNT,
121 },
122 },
123 ],
124 };
186125
187 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);126 const feeAssetItem = 0;
188 const events = await submitTransactionAsync(randomAccount, tx);
189 const result = getGenericResult(events);127 const weightLimit = 5000000000;
190 expect(result.success).to.be.true;
191128
192 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]);129 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);
193130
194 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;131 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
195 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));
196 expect(unqFees > 0n).to.be.true;
197 });
198132
199 // Acala side133 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
200 await usingApi(
201 async (api) => {134 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
202 await waitNewBlocks(api, 3);
203 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;
204 balanceUniqueForeignTokenMiddle = BigInt(free);135 expect(unqFees > 0n).to.be.true;
205136
206 [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]);137 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
138 await helper.wait.newBlocks(3);
207139
208 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;140 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
209 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;141 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
210142
211 console.log(143 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;
144 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;
145
146 console.log(
212 '[Unique -> Acala] transaction fees on Acala: %s ACA',147 '[Unique -> Acala] transaction fees on Acala: %s ACA',
213 bigIntToDecimals(acaFees, ACALA_DECIMALS),148 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),
214 );149 );
215 console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));150 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
216 expect(acaFees == 0n).to.be.true;151 expect(acaFees == 0n).to.be.true;
217 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;152 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
218 },153 });
219 acalaOptions(),
220 );
221 });154 });
222155
223 it('Should connect to Acala and send UNQ back', async () => {156 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {
224
225 // Acala side157 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
226 await usingApi(
227 async (api) => {
228 const destination = {158 const destination = {
229 V1: {159 V1: {
230 parents: 1,160 parents: 1,
231 interior: {161 interior: {
232 X2: [162 X2: [
233 {Parachain: UNIQUE_CHAIN},163 {Parachain: UNIQUE_CHAIN},
234 {164 {
235 AccountId32: {165 AccountId32: {
236 network: 'Any',166 network: 'Any',
237 id: randomAccount.addressRaw,167 id: randomAccount.addressRaw,
238 },
239 },168 },
240 ],169 },
241 },170 ],
242 },171 },
243 };172 },
173 };
244174
245 const id = {175 const id = {
246 ForeignAsset: 0,176 ForeignAsset: 0,
247 };177 };
248178
249 const destWeight = 50000000;179 const destWeight = 50000000;
250180
251 const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight);181 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);
252 const events = await submitTransactionAsync(randomAccount, tx);
253 const result = getGenericResult(events);
254 expect(result.success).to.be.true;
255182
256 [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]);183 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
257 {184 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
258 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any;
259 balanceUniqueForeignTokenFinal = BigInt(free);
260 }
261185
262 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;186 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;
263 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;187 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;
264188
265 console.log(189 console.log(
266 '[Acala -> Unique] transaction fees on Acala: %s ACA',190 '[Acala -> Unique] transaction fees on Acala: %s ACA',
267 bigIntToDecimals(acaFees, ACALA_DECIMALS),191 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),
268 );192 );
269 console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));193 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
270194
271 expect(acaFees > 0).to.be.true;195 expect(acaFees > 0).to.be.true;
272 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;196 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
273 },197 });
274 acalaOptions(),
275 );
276198
277 // Unique side199 await helper.wait.newBlocks(3);
278 await usingApi(async (api) => {
279 await waitNewBlocks(api, 3);
280200
281 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]);201 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
282 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;202 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;
283 expect(actuallyDelivered > 0).to.be.true;203 expect(actuallyDelivered > 0).to.be.true;
284204
285 console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));205 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
286206
287 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;207 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;
288 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));208 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
289 expect(unqFees == 0n).to.be.true;209 expect(unqFees == 0n).to.be.true;
290 });
291 });210 });
292});211});
293212
294// These tests are relevant only when the foreign asset pallet is disabled213// These tests are relevant only when the foreign asset pallet is disabled
295describe_xcm('[XCM] Integration test: Unique rejects non-native tokens', () => {214describeXcm('[XCM] Integration test: Unique rejects non-native tokens', () => {
296 let alice: IKeyringPair;215 let alice: IKeyringPair;
297216
298 before(async () => {217 before(async () => {
299 await usingApi(async (api, privateKeyWrapper) => {218 await usingPlaygrounds(async (_helper, privateKey) => {
300 alice = privateKeyWrapper('//Alice');219 alice = privateKey('//Alice');
301 });220 });
302 });221 });
303222
304 it('Unique rejects tokens from the Relay', async () => {223 itSub('Unique rejects tokens from the Relay', async ({helper}) => {
305 await usingApi(async (api) => {224 await usingRelayPlaygrounds(relayUrl, async (helper) => {
306 const destination = {225 const destination = {
307 V1: {226 V1: {
308 parents: 0,227 parents: 0,
341 };260 };
342261
343 const feeAssetItem = 0;262 const feeAssetItem = 0;
263 const weightLimit = 5_000_000_000;
344264
345 const weightLimit = {265 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
346 Limited: 5_000_000_000,
347 };266 });
348267
349 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);268 const maxWaitBlocks = 3;
350 const events = await submitTransactionAsync(alice, tx);
351 const result = getGenericResult(events);
352 expect(result.success).to.be.true;
353 }, relayOptions());
354269
355 await usingApi(async api => {270 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');
356 const maxWaitBlocks = 3;
357 const dmpQueueExecutedDownward = await waitEvent(
358 api,
359 maxWaitBlocks,
360 'dmpQueue',
361 'ExecutedDownward',
362 );
363271
364 expect(272 expect(
365 dmpQueueExecutedDownward != null,273 dmpQueueExecutedDownward != null,
366 '[Relay] dmpQueue.ExecutedDownward event is expected',274 '[Relay] dmpQueue.ExecutedDownward event is expected',
367 ).to.be.true;275 ).to.be.true;
368276
369 const event = dmpQueueExecutedDownward!.event;277 const event = dmpQueueExecutedDownward!.event;
370 const outcome = event.data[1] as XcmV2TraitsOutcome;278 const outcome = event.data[1] as XcmV2TraitsOutcome;
371279
372 expect(280 expect(
373 outcome.isIncomplete,281 outcome.isIncomplete,
374 '[Relay] The outcome of the XCM should be `Incomplete`',282 '[Relay] The outcome of the XCM should be `Incomplete`',
375 ).to.be.true;283 ).to.be.true;
376284
377 const incomplete = outcome.asIncomplete;285 const incomplete = outcome.asIncomplete;
378 expect(286 expect(
379 incomplete[1].toString() == 'AssetNotFound',287 incomplete[1].toString() == 'AssetNotFound',
380 '[Relay] The XCM error should be `AssetNotFound`',288 '[Relay] The XCM error should be `AssetNotFound`',
381 ).to.be.true;289 ).to.be.true;
382 });
383 });290 });
384291
385 it('Unique rejects ACA tokens from Acala', async () => {292 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {
386 await usingApi(async (api) => {293 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
387 const destination = {294 const destination = {
388 V1: {295 V1: {
389 parents: 1,296 parents: 1,
407314
408 const destWeight = 50000000;315 const destWeight = 50000000;
409316
410 const tx = api.tx.xTokens.transfer(id as any, 100_000_000_000, destination, destWeight);317 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);
411 const events = await submitTransactionAsync(alice, tx);318 });
412 const result = getGenericResult(events);
413 expect(result.success).to.be.true;
414 }, acalaOptions());
415319
416 await usingApi(async api => {320 const maxWaitBlocks = 3;
417 const maxWaitBlocks = 3;
418 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');
419321
420 expect(322 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');
421 xcmpQueueFailEvent != null,
422 '[Acala] xcmpQueue.FailEvent event is expected',
423 ).to.be.true;
424323
425 const event = xcmpQueueFailEvent!.event;324 expect(
325 xcmpQueueFailEvent != null,
426 const outcome = event.data[1] as XcmV2TraitsError;326 '[Acala] xcmpQueue.FailEvent event is expected',
327 ).to.be.true;
427328
428 expect(329 const event = xcmpQueueFailEvent!.event;
330 const outcome = event.data[1] as XcmV2TraitsError;
331
332 expect(
429 outcome.isUntrustedReserveLocation,333 outcome.isUntrustedReserveLocation,
430 '[Acala] The XCM error should be `UntrustedReserveLocation`',334 '[Acala] The XCM error should be `UntrustedReserveLocation`',
431 ).to.be.true;335 ).to.be.true;
432 });
433 });336 });
434});337});
435338
436describe_xcm('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {339describeXcm('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {
437340
438 // Unique constants341 // Unique constants
439 let uniqueAlice: IKeyringPair;342 let uniqueDonor: IKeyringPair;
440 let uniqueAssetLocation;343 let uniqueAssetLocation;
441344
442 let randomAccountUnique: IKeyringPair;345 let randomAccountUnique: IKeyringPair;
445 // Moonbeam constants348 // Moonbeam constants
446 let assetId: string;349 let assetId: string;
447350
448 const moonbeamKeyring = new Keyring({type: 'ethereum'});
449 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
450 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
451 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
452
453 const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
454 const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
455 const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
456
457 const councilVotingThreshold = 2;351 const councilVotingThreshold = 2;
458 const technicalCommitteeThreshold = 2;352 const technicalCommitteeThreshold = 2;
459 const votingPeriod = 3;353 const votingPeriod = 3;
464 symbol: 'xcUNQ',358 symbol: 'xcUNQ',
465 decimals: 18,359 decimals: 18,
466 isFrozen: false,360 isFrozen: false,
467 minimalBalance: 1,361 minimalBalance: 1n,
468 };362 };
469363
470 let balanceUniqueTokenInit: bigint;364 let balanceUniqueTokenInit: bigint;
478 let balanceGlmrTokenFinal: bigint;372 let balanceGlmrTokenFinal: bigint;
479373
480 before(async () => {374 before(async () => {
481 await usingApi(async (api, privateKeyWrapper) => {375 await usingPlaygrounds(async (helper, privateKey) => {
482 const keyringEth = new Keyring({type: 'ethereum'});376 uniqueDonor = privateKey('//Alice');
483 const keyringSr25519 = new Keyring({type: 'sr25519'});377 [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);
484378
485 uniqueAlice = privateKeyWrapper('//Alice');
486 randomAccountUnique = generateKeyringPair(keyringSr25519);
487 randomAccountMoonbeam = generateKeyringPair(keyringEth);
488
489 balanceForeignUnqTokenInit = 0n;379 balanceForeignUnqTokenInit = 0n;
490 });380 });
491381
492 await usingApi(382 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
493 async (api) => {383 const alithAccount = helper.account.alithAccount();
384 const baltatharAccount = helper.account.baltatharAccount();
385 const dorothyAccount = helper.account.dorothyAccount();
494386
495 // >>> Sponsoring Dorothy >>>387 randomAccountMoonbeam = helper.account.create();
496 console.log('Sponsoring Dorothy.......');
497 const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);
498 const events0 = await submitTransactionAsync(alithAccount, tx0);
499 const result0 = getGenericResult(events0);
500 expect(result0.success).to.be.true;
501 console.log('Sponsoring Dorothy.......DONE');
502 // <<< Sponsoring Dorothy <<<
503388
504 const sourceLocation: MultiLocation = api.createType(389 // >>> Sponsoring Dorothy >>>
505 'MultiLocation',390 console.log('Sponsoring Dorothy.......');
506 {391 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
507 parents: 1,392 console.log('Sponsoring Dorothy.......DONE');
508 interior: {X1: {Parachain: UNIQUE_CHAIN}},393 // <<< Sponsoring Dorothy <<<
509 },
510 );
511394
512 uniqueAssetLocation = {XCM: sourceLocation};395 uniqueAssetLocation = {
396 XCM: {
397 parents: 1,
398 interior: {X1: {Parachain: UNIQUE_CHAIN}},
513 const existentialDeposit = 1;399 },
400 };
401 const existentialDeposit = 1n;
514 const isSufficient = true;402 const isSufficient = true;
515 const unitsPerSecond = '1';403 const unitsPerSecond = 1n;
516 const numAssetsWeightHint = 0;404 const numAssetsWeightHint = 0;
517405
518 const registerTx = api.tx.assetManager.registerForeignAsset(406 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
519 uniqueAssetLocation,407 location: uniqueAssetLocation,
520 uniqueAssetMetadata,408 metadata: uniqueAssetMetadata,
521 existentialDeposit,409 existentialDeposit,
522 isSufficient,410 isSufficient,
523 );411 unitsPerSecond,
524 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');412 numAssetsWeightHint,
413 });
414 const proposalHash = blake2AsHex(encodedProposal);
525415
526 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(416 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
527 uniqueAssetLocation,
528 unitsPerSecond,
529 numAssetsWeightHint,417 console.log('Encoded length %d', encodedProposal.length);
530 );
531 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');418 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
532419
533 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);420 // >>> Note motion preimage >>>
421 console.log('Note motion preimage.......');
422 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
534 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');423 console.log('Note motion preimage.......DONE');
424 // <<< Note motion preimage <<<
535425
536 // >>> Note motion preimage >>>426 // >>> Propose external motion through council >>>
537 console.log('Note motion preimage.......');427 console.log('Propose external motion through council.......');
538 const encodedProposal = batchCall?.method.toHex() || '';428 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);
539 const proposalHash = blake2AsHex(encodedProposal);429 const encodedMotion = externalMotion?.method.toHex() || '';
540 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);430 const motionHash = blake2AsHex(encodedMotion);
541 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);431 console.log('Motion hash is %s', motionHash);
542 console.log('Encoded length %d', encodedProposal.length);
543432
544 const tx1 = api.tx.democracy.notePreimage(encodedProposal);433 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);
545 const events1 = await submitTransactionAsync(baltatharAccount, tx1);
546 const result1 = getGenericResult(events1);
547 expect(result1.success).to.be.true;
548 console.log('Note motion preimage.......DONE');
549 // <<< Note motion preimage <<<
550434
551 // >>> Propose external motion through council >>>435 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;
552 console.log('Propose external motion through council.......');
553 const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);
554 const tx2 = api.tx.councilCollective.propose(436 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
555 councilVotingThreshold,
556 externalMotion,
557 externalMotion.encodedLength,
558 );
559 const events2 = await submitTransactionAsync(baltatharAccount, tx2);437 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
560 const result2 = getGenericResult(events2);
561 expect(result2.success).to.be.true;
562438
563 const encodedMotion = externalMotion?.method.toHex() || '';439 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);
564 const motionHash = blake2AsHex(encodedMotion);440 console.log('Propose external motion through council.......DONE');
565 console.log('Motion hash is %s', motionHash);441 // <<< Propose external motion through council <<<
566442
567 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);443 // >>> Fast track proposal through technical committee >>>
568 {
569 const events3 = await submitTransactionAsync(dorothyAccount, tx3);444 console.log('Fast track proposal through technical committee.......');
570 const result3 = getGenericResult(events3);445 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
571 expect(result3.success).to.be.true;
572 }446 const encodedFastTrack = fastTrack?.method.toHex() || '';
573 {
574 const events3 = await submitTransactionAsync(baltatharAccount, tx3);
575 const result3 = getGenericResult(events3);447 const fastTrackHash = blake2AsHex(encodedFastTrack);
576 expect(result3.success).to.be.true;448 console.log('FastTrack hash is %s', fastTrackHash);
577 }
578449
579 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);450 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
580 const events4 = await submitTransactionAsync(dorothyAccount, tx4);
581 const result4 = getGenericResult(events4);
582 expect(result4.success).to.be.true;
583 console.log('Propose external motion through council.......DONE');
584 // <<< Propose external motion through council <<<
585451
586 // >>> Fast track proposal through technical committee >>>452 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;
587 console.log('Fast track proposal through technical committee.......');
588 const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
589 const tx5 = api.tx.techCommitteeCollective.propose(453 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
590 technicalCommitteeThreshold,
591 fastTrack,
592 fastTrack.encodedLength,
593 );
594 const events5 = await submitTransactionAsync(alithAccount, tx5);454 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
595 const result5 = getGenericResult(events5);
596 expect(result5.success).to.be.true;
597455
598 const encodedFastTrack = fastTrack?.method.toHex() || '';456 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);
599 const fastTrackHash = blake2AsHex(encodedFastTrack);457 console.log('Fast track proposal through technical committee.......DONE');
600 console.log('FastTrack hash is %s', fastTrackHash);458 // <<< Fast track proposal through technical committee <<<
601459
602 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;460 // >>> Referendum voting >>>
603 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);461 console.log('Referendum voting.......');
604 {462 await helper.democracy.referendumVote(dorothyAccount, 0, {
605 const events6 = await submitTransactionAsync(baltatharAccount, tx6);
606 const result6 = getGenericResult(events6);
607 expect(result6.success).to.be.true;463 balance: 10_000_000_000_000_000_000n,
608 }464 vote: {aye: true, conviction: 1},
609 {
610 const events6 = await submitTransactionAsync(alithAccount, tx6);
611 const result6 = getGenericResult(events6);465 });
612 expect(result6.success).to.be.true;466 console.log('Referendum voting.......DONE');
613 }467 // <<< Referendum voting <<<
614468
615 const tx7 = api.tx.techCommitteeCollective469 // >>> Acquire Unique AssetId Info on Moonbeam >>>
616 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);
617 const events7 = await submitTransactionAsync(baltatharAccount, tx7);470 console.log('Acquire Unique AssetId Info on Moonbeam.......');
618 const result7 = getGenericResult(events7);
619 expect(result7.success).to.be.true;
620 console.log('Fast track proposal through technical committee.......DONE');
621 // <<< Fast track proposal through technical committee <<<
622471
623 // >>> Referendum voting >>>472 // Wait for the democracy execute
624 console.log('Referendum voting.......');
625 const tx8 = api.tx.democracy.vote(
626 0,
627 {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},473 await helper.wait.newBlocks(5);
628 );
629 const events8 = await submitTransactionAsync(dorothyAccount, tx8);
630 const result8 = getGenericResult(events8);
631 expect(result8.success).to.be.true;
632 console.log('Referendum voting.......DONE');
633 // <<< Referendum voting <<<
634474
635 // >>> Acquire Unique AssetId Info on Moonbeam >>>475 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
636 console.log('Acquire Unique AssetId Info on Moonbeam.......');
637476
638 // Wait for the democracy execute477 console.log('UNQ asset ID is %s', assetId);
478 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
639 await waitNewBlocks(api, 5);479 // >>> Acquire Unique AssetId Info on Moonbeam >>>
640480
641 assetId = (await api.query.assetManager.assetTypeId({481 // >>> Sponsoring random Account >>>
482 console.log('Sponsoring random Account.......');
642 XCM: sourceLocation,483 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);
643 })).toString();484 console.log('Sponsoring random Account.......DONE');
485 // <<< Sponsoring random Account <<<
644486
645 console.log('UNQ asset ID is %s', assetId);487 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);
646 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
647 // >>> Acquire Unique AssetId Info on Moonbeam >>>488 });
648489
649 // >>> Sponsoring random Account >>>490 await usingPlaygrounds(async (helper) => {
650 console.log('Sponsoring random Account.......');
651 const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);
652 const events10 = await submitTransactionAsync(baltatharAccount, tx10);
653 const result10 = getGenericResult(events10);
654 expect(result10.success).to.be.true;
655 console.log('Sponsoring random Account.......DONE');
656 // <<< Sponsoring random Account <<<
657
658 [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]);
659 },
660 moonbeamOptions(),
661 );
662
663 await usingApi(async (api) => {
664 const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT);491 await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);
665 const events0 = await submitTransactionAsync(uniqueAlice, tx0);492 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
666 const result0 = getGenericResult(events0);
667 expect(result0.success).to.be.true;
668
669 [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]);
670 });493 });
671 });494 });
672495
673 it('Should connect and send UNQ to Moonbeam', async () => {496 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {
674 await usingApi(async (api) => {
675 const currencyId = {497 const currencyId = {
676 NativeAssetId: 'Here',498 NativeAssetId: 'Here',
677 };499 };
678 const dest = {500 const dest = {
679 V1: {501 V1: {
680 parents: 1,502 parents: 1,
681 interior: {503 interior: {
682 X2: [504 X2: [
683 {Parachain: MOONBEAM_CHAIN},505 {Parachain: MOONBEAM_CHAIN},
684 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},506 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},
685 ],507 ],
686 },
687 },508 },
688 };509 },
689 const amount = TRANSFER_AMOUNT;510 };
511 const amount = TRANSFER_AMOUNT;
690 const destWeight = 850000000;512 const destWeight = 850000000;
691513
692 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);514 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, destWeight);
693 const events = await submitTransactionAsync(randomAccountUnique, tx);
694 const result = getGenericResult(events);
695 expect(result.success).to.be.true;
696515
697 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);516 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);
698 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;517 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;
699518
700 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;519 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
701 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));520 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));
702 expect(transactionFees > 0).to.be.true;521 expect(transactionFees > 0).to.be.true;
703 });
704522
705 await usingApi(523 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
706 async (api) => {
707 await waitNewBlocks(api, 3);524 await helper.wait.newBlocks(3);
708525
709 [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);526 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);
710527
711 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;528 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;
712 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));529 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));
713 expect(glmrFees == 0n).to.be.true;530 expect(glmrFees == 0n).to.be.true;
714531
715 const unqRandomAccountAsset = (532 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;
716 await api.query.assets.account(assetId, randomAccountMoonbeam.address)
717 ).toJSON()! as any;
718533
719 balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);534 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;
720 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;
721 console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));535 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
722 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;536 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
723 },537 });
724 moonbeamOptions(),
725 );
726 });538 });
727539
728 it('Should connect to Moonbeam and send UNQ back', async () => {540 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {
729 await usingApi(541 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
730 async (api) => {
731 const asset = {542 const asset = {
732 V1: {543 V1: {
733 id: {544 id: {
734 Concrete: {545 Concrete: {
735 parents: 1,546 parents: 1,
736 interior: {547 interior: {
737 X1: {Parachain: UNIQUE_CHAIN},548 X1: {Parachain: UNIQUE_CHAIN},
738 },
739 },549 },
740 },550 },
741 fun: {
742 Fungible: TRANSFER_AMOUNT,
743 },
744 },551 },
745 };552 fun: {
746 const destination = {
747 V1: {
748 parents: 1,553 Fungible: TRANSFER_AMOUNT,
749 interior: {
750 X2: [
751 {Parachain: UNIQUE_CHAIN},
752 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},
753 ],
754 },
755 },554 },
756 };555 },
556 };
757 const destWeight = 50000000;557 const destination = {
558 V1: {
559 parents: 1,
560 interior: {
561 X2: [
562 {Parachain: UNIQUE_CHAIN},
563 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},
564 ],
565 },
566 },
567 };
568 const destWeight = 50000000;
758569
759 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);570 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, destWeight);
760 const events = await submitTransactionAsync(randomAccountMoonbeam, tx);
761 const result = getGenericResult(events);
762 expect(result.success).to.be.true;
763571
764 [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);572 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);
765573
766 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;574 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;
767 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));575 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));
768 expect(glmrFees > 0).to.be.true;576 expect(glmrFees > 0).to.be.true;
769577
770 const unqRandomAccountAsset = (578 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);
771 await api.query.assets.account(assetId, randomAccountMoonbeam.address)
772 ).toJSON()! as any;
773579
774 expect(unqRandomAccountAsset).to.be.null;580 expect(unqRandomAccountAsset).to.be.null;
581
582 balanceForeignUnqTokenFinal = 0n;
775583
776 balanceForeignUnqTokenFinal = 0n;584 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;
585 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
586 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
587 });
777588
778 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;589 await helper.wait.newBlocks(3);
779 console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));
780 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
781 },
782 moonbeamOptions(),
783 );
784590
785 await usingApi(async (api) => {591 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);
786 await waitNewBlocks(api, 3);592 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;
593 expect(actuallyDelivered > 0).to.be.true;
787594
788 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);595 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
789 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;
790 expect(actuallyDelivered > 0).to.be.true;
791596
792 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));597 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;
793
794 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;
795 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));598 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
796 expect(unqFees == 0n).to.be.true;599 expect(unqFees == 0n).to.be.true;
797 });
798 });600 });
799});601});
800602