git.delta.rocks / unique-network / refs/commits / 6360d02576ab

difftreelog

Merge branch 'develop' into tests/feed-alices

Fahrrader2022-10-17parents: #520f20e #d8e7f8d.patch.diff
in: master

47 files changed

added.docker/additional/xcm-rococo/.envdiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/Dockerfile-xcm-opal-rococodiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/Dockerfile-xcm-quartz-rococodiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/Dockerfile-xcm-unique-rococodiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/docker-compose-xcm-opal-rococo.ymldiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/docker-compose-xcm-quartz-rococo.ymldiffbeforeafterboth

no changes

added.docker/additional/xcm-rococo/docker-compose-xcm-unique-rococo.ymldiffbeforeafterboth

no changes

added.docker/xcm-config/launch-config-xcm-opal-rococo.jsondiffbeforeafterboth

no changes

added.docker/xcm-config/launch-config-xcm-quartz-rococo.jsondiffbeforeafterboth

no changes

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

no syntactic changes

modified.envdiffbeforeafterboth
1RUST_TOOLCHAIN=nightly-2022-07-241RUST_TOOLCHAIN=nightly-2022-07-24
2POLKADOT_BUILD_BRANCH=release-v0.9.272POLKADOT_BUILD_BRANCH=release-v0.9.29
33
4POLKADOT_MAINNET_BRANCH=release-v0.9.264POLKADOT_MAINNET_BRANCH=release-v0.9.26
5UNIQUE_MAINNET_TAG=v9240105UNIQUE_MAINNET_TAG=v924010
13QUARTZ_REPLICA_FROM=wss://eu-ws-quartz.unique.network:44313QUARTZ_REPLICA_FROM=wss://eu-ws-quartz.unique.network:443
14UNIQUE_REPLICA_FROM=wss://eu-ws.unique.network:44314UNIQUE_REPLICA_FROM=wss://eu-ws.unique.network:443
1515
16POLKADOT_LAUNCH_BRANCH=feature/rewrite-chain-id-in-spec16POLKADOT_LAUNCH_BRANCH=unique-network
1717
18KARURA_BUILD_BRANCH=2.9.118KARURA_BUILD_BRANCH=2.9.1
19ACALA_BUILD_BRANCH=2.9.219ACALA_BUILD_BRANCH=2.9.2
modified.github/workflows/testnet-build.ymldiffbeforeafterboth
125 run: docker pull uniquenetwork/builder-polkadot:${{ env.POLKADOT_BUILD_BRANCH }} 125 run: docker pull uniquenetwork/builder-polkadot:${{ env.POLKADOT_BUILD_BRANCH }}
126126
127 - name: Build the stack127 - name: Build the stack
128 run: cd .docker/ && docker build --file ./Dockerfile-testnet.${{ matrix.network }}.yml --tag uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} --tag uniquenetwork/${{ matrix.network }}-testnet-local:latest . 128 run: cd .docker/ && docker build --file ./Dockerfile-testnet.${{ matrix.network }}.yml --tag uniquenetwork/${{ matrix.network }}-testnet-local-nightly:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} .
129129
130 - name: Push docker version image130 - name: Push docker version image
131 run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }}131 run: docker push uniquenetwork/${{ matrix.network }}-testnet-local-nightly:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }}
132
133 - name: Push docker latest image
134 run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:latest
135132
136 - name: Clean Workspace133 - name: Clean Workspace
137 if: always()134 if: always()
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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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, 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 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.seqtest.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.seqtest.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.seqtest.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.seqtest.tsdiffbeforeafterboth
7 rejectNft,7 rejectNft,
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: 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.seqtest.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 {4import {
6 acceptResourceRemoval, addNftBasicResource, createCollection, mintNft, removeNftResource, sendNft,5 acceptResourceRemoval, addNftBasicResource, createCollection, mintNft, removeNftResource, sendNft,
7} from './util/tx';6} from './util/tx';
337 await expectTxFailure(/rmrkCore\.NoPermission/, tx);336 await expectTxFailure(/rmrkCore\.NoPermission/, tx);
338 });337 });
339338
340 after(() => {339 after(async() => { await api.disconnect(); });
341 api.disconnect();
342 });
343});340});
modifiedtests/src/rmrk/rmrkIsolation.seqtest.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';
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 itRmrk = (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 = await privateKey('//Alice');
67 await requirePallets(this, [Pallets.RmrkCore]);
68 });65 });
69 });66 });
7067
71 itRmrk('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 itRmrk = (await isUnique() ? it : it.skip);87 before(async function() {
88 await usingPlaygrounds(async (helper, privateKey) => {
89 alice = await privateKey('//Alice');
90 bob = await privateKey('//Bob');
91 normalizedAlice = {Substrate: helper.address.normalizeSubstrateToChainFormat(alice.address)};
9292
93 before(async () => {93 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 itRmrk('[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 itRmrk('[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 itRmrk = (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 = await privateKey('//Alice');
233 bob = privateKeyWrapper('//Bob');206 bob = await 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 itRmrk('[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.seqtest.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.seqtest.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.seqtest.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.seqtest.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.seqtest.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', () => {
53 await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx);52 await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx);
54 });53 });
5554
56 after(() => { api.disconnect(); });55 after(async() => { await api.disconnect(); });
57});56});
5857
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/index.tsdiffbeforeafterboth
8import chaiAsPromised from 'chai-as-promised';8import chaiAsPromised from 'chai-as-promised';
9import {Context} from 'mocha';9import {Context} from 'mocha';
10import config from '../config';10import config from '../config';
11import '../interfaces/augment-api-events';11import {ChainHelperBase} from './playgrounds/unique';
12import {ILogger} from './playgrounds/types';
12import {DevUniqueHelper, SilentLogger, SilentConsole} from './playgrounds/unique.dev';13import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper} from './playgrounds/unique.dev';
1314
14chai.use(chaiAsPromised);15chai.use(chaiAsPromised);
15export const expect = chai.expect;16export const expect = chai.expect;
22 return `//Alice+${getTestHash(filename)}`;23 return `//Alice+${getTestHash(filename)}`;
23};24};
2425
25export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<void>, url: string = config.substrateUrl) => {26async function usingPlaygroundsGeneral<T extends ChainHelperBase>(helperType: new(logger: ILogger) => T, url: string, code: (helper: T, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<void>) {
26 const silentConsole = new SilentConsole();27 const silentConsole = new SilentConsole();
27 silentConsole.enable();28 silentConsole.enable();
2829
29 const helper = new DevUniqueHelper(new SilentLogger());30 const helper = new helperType(new SilentLogger());
3031
31 try {32 try {
32 await helper.connect(url);33 await helper.connect(url);
38 else {39 else {
39 const actualSeed = getTestSeed(seed.filename);40 const actualSeed = getTestSeed(seed.filename);
40 let account = helper.util.fromSeed(actualSeed, ss58Format);41 let account = helper.util.fromSeed(actualSeed, ss58Format);
42 // here's to hoping that no
41 if (!seed.ignoreFundsPresence && await helper.balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND) {43 if (!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) {
42 console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);44 console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);
43 account = helper.util.fromSeed('//Alice', ss58Format);45 account = helper.util.fromSeed('//Alice', ss58Format);
44 }46 }
51 await helper.disconnect();53 await helper.disconnect();
52 silentConsole.disable();54 silentConsole.disable();
53 }55 }
54};56}
57
58export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<void>, url: string = config.substrateUrl) => {
59 return usingPlaygroundsGeneral<DevUniqueHelper>(DevUniqueHelper, url, code);
60};
61
62export const usingWestmintPlaygrounds = async (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
63 return usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);
64};
65
66export const usingRelayPlaygrounds = async (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
67 return usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);
68};
69
70export const usingAcalaPlaygrounds = async (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
71 return usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);
72};
73
74export const usingKaruraPlaygrounds = async (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
75 return usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);
76};
77
78export const usingMoonbeamPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
79 return usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);
80};
81
82export const usingMoonriverPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
83 return usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);
84};
5585
56export const MINIMUM_DONOR_FUND = 100_000n;86export const MINIMUM_DONOR_FUND = 100_000n;
57export const DONOR_FUNDING = 1_000_000n;87export const DONOR_FUNDING = 1_000_000n;
99itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});129itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});
100itSub.ifWithPallets = itSubIfWithPallet;130itSub.ifWithPallets = itSubIfWithPallet;
131
132export async function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {
133 (process.env.RUN_XCM_TESTS && !opts.skip
134 ? describe
135 : describe.skip)(title, fn);
136}
137
138describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true});
101139
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
33
4import {stringToU8a} from '@polkadot/util';4import {stringToU8a} from '@polkadot/util';
5import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';5import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
6import {UniqueHelper} from './unique';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique';
7import {ApiPromise, WsProvider} from '@polkadot/api';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
8import * as defs from '../../interfaces/definitions';8import * as defs from '../../interfaces/definitions';
9import {IKeyringPair} from '@polkadot/types/types';9import {IKeyringPair} from '@polkadot/types/types';
10import {EventRecord} from '@polkadot/types/interfaces';
10import {ICrossAccountId} from './types';11import {ICrossAccountId} from './types';
12import {FrameSystemEventRecord} from '@polkadot/types/lookup';
1113
12export class SilentLogger {14export class SilentLogger {
13 log(_msg: any, _level: any): void { }15 log(_msg: any, _level: any): void { }
109 }110 }
110}111}
112
113export class DevRelayHelper extends RelayHelper {}
114
115export class DevWestmintHelper extends WestmintHelper {
116 wait: WaitGroup;
117
118 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
119 options.helperBase = options.helperBase ?? DevWestmintHelper;
120
121 super(logger, options);
122 this.wait = new WaitGroup(this);
123 }
124}
125
126export class DevMoonbeamHelper extends MoonbeamHelper {
127 account: MoonbeamAccountGroup;
128 wait: WaitGroup;
129
130 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
131 options.helperBase = options.helperBase ?? DevMoonbeamHelper;
132
133 super(logger, options);
134 this.account = new MoonbeamAccountGroup(this);
135 this.wait = new WaitGroup(this);
136 }
137}
138
139export class DevMoonriverHelper extends DevMoonbeamHelper {}
140
141export class DevAcalaHelper extends AcalaHelper {
142 wait: WaitGroup;
143
144 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
145 options.helperBase = options.helperBase ?? DevAcalaHelper;
146
147 super(logger, options);
148 this.wait = new WaitGroup(this);
149 }
150}
151
152export class DevKaruraHelper extends DevAcalaHelper {}
111153
112class ArrangeGroup {154class ArrangeGroup {
113 helper: DevUniqueHelper;155 helper: DevUniqueHelper;
252 }294 }
253}295}
296
297class MoonbeamAccountGroup {
298 helper: MoonbeamHelper;
299
300 keyring: Keyring;
301 _alithAccount: IKeyringPair;
302 _baltatharAccount: IKeyringPair;
303 _dorothyAccount: IKeyringPair;
304
305 constructor(helper: MoonbeamHelper) {
306 this.helper = helper;
307
308 this.keyring = new Keyring({type: 'ethereum'});
309 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
310 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
311 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
312
313 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
314 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
315 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
316 }
317
318 alithAccount() {
319 return this._alithAccount;
320 }
321
322 baltatharAccount() {
323 return this._baltatharAccount;
324 }
325
326 dorothyAccount() {
327 return this._dorothyAccount;
328 }
329
330 create() {
331 return this.keyring.addFromUri(mnemonicGenerate());
332 }
333}
254334
255class WaitGroup {335class WaitGroup {
256 helper: DevUniqueHelper;336 helper: ChainHelperBase;
257337
258 constructor(helper: DevUniqueHelper) {338 constructor(helper: ChainHelperBase) {
259 this.helper = helper;339 this.helper = helper;
260 }340 }
261341
304 });384 });
305 }385 }
386
387 async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
388 // eslint-disable-next-line no-async-promise-executor
389 const promise = new Promise<EventRecord | null>(async (resolve) => {
390 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
391 const blockNumber = header.number.toHuman();
392 const blockHash = header.hash;
393 const eventIdStr = `${eventSection}.${eventMethod}`;
394 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
395
396 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
397
398 const apiAt = await this.helper.getApi().at(blockHash);
399 const eventRecords = (await apiAt.query.system.events()) as any;
400
401 const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {
402 return r.event.section == eventSection && r.event.method == eventMethod;
403 });
404
405 if (neededEvent) {
406 unsubscribe();
407 resolve(neededEvent);
408 } else if (maxBlocksToWait > 0) {
409 maxBlocksToWait--;
410 } else {
411 this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);
412
413 unsubscribe();
414 resolve(null);
415 }
416 });
417 });
418 return promise;
419 }
306}420}
307421
308class AdminGroup {422class 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, describeXCM, getGenericResult, paraSiblingSovereignAccount, normalizeAccountId} from './../deprecated-helpers/helpers';19import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util';
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
6958
7059
71 before(async () => {60 before(async () => {
72 console.log('hey babe its opal');61 await usingPlaygrounds(async (_helper, privateKey) => {
73 await usingApi(async (api, privateKeyWrapper) => {
74 alice = privateKeyWrapper('//Alice');62 alice = await privateKey('//Alice');
75 bob = privateKeyWrapper('//Bob'); // funds donor63 bob = await privateKey('//Bob'); // funds donor
76 });64 });
7765
78 const statemineApiOptions: ApiOptions = {66 await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
79 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
80 };
81
82 const uniqueApiOptions: ApiOptions = {
83 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
84 };
85
86 const relayApiOptions: ApiOptions = {
87 provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT),
88 };
89
90 await usingApi(async (api) => {
91
92 // 350.00 (three hundred fifty) DOT67 // 350.00 (three hundred fifty) DOT
93 const fundingAmount = 3_500_000_000_000; 68 const fundingAmount = 3_500_000_000_000n;
9469
95 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);
96 const events = await executeTransaction(api, alice, tx);71 await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
97 const result = getGenericResult(events);72 await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
98 expect(result.success).to.be.true;
9973
100 // set metadata
101 const tx2 = api.tx.assets.setMetadata(ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
102 const events2 = await executeTransaction(api, alice, tx2);
103 const result2 = getGenericResult(events2);
104 expect(result2.success).to.be.true;
105
106 // mint some amount of asset
107 const tx3 = api.tx.assets.mint(ASSET_ID, alice.addressRaw, ASSET_AMOUNT);
108 const events3 = await executeTransaction(api, alice, tx3);
109 const result3 = getGenericResult(events3);
110 expect(result3.success).to.be.true;
111
112 // funding parachain sovereing account (Parachain: 2095)74 // funding parachain sovereing account (Parachain: 2095)
113 const parachainSovereingAccount = await paraSiblingSovereignAccount(UNIQUE_CHAIN);75 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
114 const tx4 = api.tx.balances.transfer(parachainSovereingAccount, fundingAmount);76 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
115 const events4 = await executeTransaction(api, bob, tx4);77 });
116 const result4 = getGenericResult(events4);
117 expect(result4.success).to.be.true;
11878
119 }, statemineApiOptions);
12079
121
122 await usingApi(async (api) => {80 await usingPlaygrounds(async (helper) => {
123
124 const location = {81 const location = {
125 V1: {82 V1: {
145 decimals: ASSET_METADATA_DECIMALS,102 decimals: ASSET_METADATA_DECIMALS,
146 minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,103 minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
147 };104 };
148 //registerForeignAsset(owner, location, metadata)105 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
149 const tx = api.tx.foreignAssets.registerForeignAsset(alice.addressRaw, location, metadata);
150 const sudoTx = api.tx.sudo.sudo(tx as any);106 balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
151 const events = await executeTransaction(api, alice, sudoTx);
152 const result = getGenericResult(events);107 });
153 expect(result.success).to.be.true;
154108
155 [balanceOpalBefore] = await getBalance(api, [alice.address]);
156109
157 }, uniqueApiOptions);
158
159
160 // Providing the relay currency to the unique sender account110 // Providing the relay currency to the unique sender account
161 await usingApi(async (api) => {111 await usingRelayPlaygrounds(relayUrl, async (helper) => {
162 const destination = {112 const destination = {
163 V1: {113 V1: {
164 parents: 0,114 parents: 0,
197 };147 };
198148
199 const feeAssetItem = 0;149 const feeAssetItem = 0;
150 const weightLimit = 5_000_000_000;
200151
201 const weightLimit = {152 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
202 Limited: 5_000_000_000,
203 };
204
205 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
206 const events = await executeTransaction(api, alice, tx);153 });
207 const result = getGenericResult(events);
208 expect(result.success).to.be.true;
209 }, relayApiOptions);
210 154
211 });155 });
212156
213 it('Should connect and send USDT from Westmint to Opal', async () => {157 itSub('Should connect and send USDT from Westmint to Opal', async ({helper}) => {
214 158 await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
215 const statemineApiOptions: ApiOptions = {
216 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
217 };
218
219 const uniqueApiOptions: ApiOptions = {
220 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
221 };
222
223 await usingApi(async (api) => {
224
225 const dest = {159 const dest = {
226 V1: {160 V1: {
268 };202 };
269203
270 const feeAssetItem = 0;204 const feeAssetItem = 0;
205 const weightLimit = 5000000000;
271206
272 const weightLimit = {207 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);
273 Limited: 5000000000,208 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, weightLimit);
274 };
275209
276 [balanceStmnBefore] = await getBalance(api, [alice.address]);210 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);
277211
278 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(dest, beneficiary, assets, feeAssetItem, weightLimit);
279 const events = await executeTransaction(api, alice, tx);
280 const result = getGenericResult(events);
281 expect(result.success).to.be.true;
282
283 [balanceStmnAfter] = await getBalance(api, [alice.address]);
284
285 // common good parachain take commission in it native token212 // common good parachain take commission in it native token
286 console.log(213 console.log(
287 'Opal to Westmint transaction fees on Westmint: %s WND',214 'Opal to Westmint transaction fees on Westmint: %s WND',
288 bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS),215 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS),
289 );216 );
290 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;217 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
291218
292 }, statemineApiOptions);219 });
293220
294221
295 // ensure that asset has been delivered222 // ensure that asset has been delivered
296 await usingApi(async (api) => {223 await helper.wait.newBlocks(3);
297 await waitNewBlocks(api, 3);
298 // expext collection id will be with id 1
299 const free = (await api.query.fungible.balance(1, normalizeAccountId(alice.address))).toBigInt();
300224
301 [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});
302227
303 // commission has not paid in USDT token228 balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
304 expect(free == TRANSFER_AMOUNT).to.be.true;
305 console.log(
306 'Opal to Westmint transaction fees on Opal: %s USDT',
307 bigIntToDecimals(TRANSFER_AMOUNT - free),
308 );
309 // ... and parachain native token
310 expect(balanceOpalAfter == balanceOpalBefore).to.be.true;
311 console.log(
312 'Opal to Westmint transaction fees on Opal: %s WND',
313 bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS),
314 );
315229
316 }, 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),
317 241 );
318 });242 });
319243
320 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}) => {
321
322 const uniqueApiOptions: ApiOptions = {
323 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
324 };
325
326 const statemineApiOptions: ApiOptions = {
327 provider: new WsProvider('ws://127.0.0.1:' + STATEMINE_PORT),
328 };
329
330 await usingApi(async (api) => {
331 const destination = {245 const destination = {
332 V1: {246 V1: {
333 parents: 1,247 parents: 1,
334 interior: {X2: [248 interior: {X2: [
335 {
336 Parachain: STATEMINE_CHAIN,
337 },
338 {
339 AccountId32: {
340 network: 'Any',
341 id: alice.addressRaw,
342 },
343 },
344 ]},
345 },
346 };
347
348 const currencies: [any, bigint][] = [
349 [
350 {249 {
351 ForeignAssetId: 0,250 Parachain: STATEMINE_CHAIN,
352 },251 },
353 //10_000_000_000_000_000n,
354 TRANSFER_AMOUNT,
355 ],
356 [
357 {252 {
358 NativeAssetId: 'Parent',253 AccountId32: {
254 network: 'Any',
255 id: alice.addressRaw,
256 },
359 },257 },
360 400_000_000_000_000n,258 ]},
361 ],259 },
362 ];260 };
363261
364 const feeItem = 1;262 const currencies: [any, bigint][] = [
365 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 ];
366277
367 const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight);278 const feeItem = 1;
368 const events = await executeTransaction(api, alice, tx);
369 const result = getGenericResult(events);279 const destWeight = 500000000000;
370 expect(result.success).to.be.true;
371
372 // the commission has been paid in parachain native token
373 [balanceOpalFinal] = await getBalance(api, [alice.address]);
374 expect(balanceOpalAfter > balanceOpalFinal).to.be.true;
375 }, uniqueApiOptions);
376280
377 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) => {
378 await waitNewBlocks(api, 3);288 await helper.wait.newBlocks(3);
379 289
380 // 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.
381 // Also check that xcm transfer has been succeeded 291 // Also check that xcm transfer has been succeeded
382 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;
383 expect(BigInt(free.balance.replace(/,/g, '')) == ASSET_AMOUNT).to.be.true;
384 }, statemineApiOptions);293 });
385 });294 });
386295
387 it('Should connect and send Relay token to Unique', async () => {296 itSub('Should connect and send Relay token to Unique', async ({helper}) => {
388
389 const uniqueApiOptions: ApiOptions = {
390 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
391 };
392
393 const uniqueApiOptions2: ApiOptions = {
394 provider: new WsProvider('ws://127.0.0.1:' + UNIQUE_PORT),
395 };
396
397 const relayApiOptions: ApiOptions = {
398 provider: new WsProvider('ws://127.0.0.1:' + RELAY_PORT),
399 };
400
401 const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;297 const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
402298
403 await usingApi(async (api) => {299 balanceBobBefore = await helper.balance.getSubstrate(bob.address);
404 [balanceBobBefore] = await getBalance(api, [bob.address]);
405 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'});
406301
407 }, uniqueApiOptions);
408
409 // Providing the relay currency to the unique sender account302 // Providing the relay currency to the unique sender account
410 await usingApi(async (api) => {303 await usingRelayPlaygrounds(relayUrl, async (helper) => {
411 const destination = {304 const destination = {
412 V1: {305 V1: {
413 parents: 0,306 parents: 0,
446 };339 };
447340
448 const feeAssetItem = 0;341 const feeAssetItem = 0;
342 const weightLimit = 5_000_000_000;
449343
450 const weightLimit = {344 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, weightLimit);
451 Limited: 5_000_000_000,
452 };
453
454 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
455 const events = await executeTransaction(api, bob, tx);345 });
456 const result = getGenericResult(events);
457 expect(result.success).to.be.true;
458 }, relayApiOptions);
459 346
347 await helper.wait.newBlocks(3);
460348
461 await usingApi(async (api) => {349 balanceBobAfter = await helper.balance.getSubstrate(bob.address);
462 await waitNewBlocks(api, 3);350 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
463351
464 [balanceBobAfter] = await getBalance(api, [bob.address]);352 const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
465 balanceBobRelayTokenAfter = BigInt(((await api.query.tokens.accounts(bob.addressRaw, {NativeAssetId: 'Parent'})).toJSON() as any).free);
466 const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
467 console.log(353 console.log(
468 'Relay (Westend) to Opal transaction fees: %s OPL',354 'Relay (Westend) to Opal transaction fees: %s OPL',
469 bigIntToDecimals(balanceBobAfter - balanceBobBefore),355 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),
470 );356 );
471 console.log(357 console.log(
472 'Relay (Westend) to Opal transaction fees: %s WND',358 'Relay (Westend) to Opal transaction fees: %s WND',
473 bigIntToDecimals(wndFee, WESTMINT_DECIMALS),359 helper.util.bigIntToDecimals(wndFee, WESTMINT_DECIMALS),
474 );360 );
475 expect(balanceBobBefore == balanceBobAfter).to.be.true;361 expect(balanceBobBefore == balanceBobAfter).to.be.true;
476 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;362 expect(balanceBobRelayTokenBefore < balanceBobRelayTokenAfter).to.be.true;
477 }, uniqueApiOptions2);
478
479 });363 });
480364
481 it('Should connect and send Relay token back', async () => {365 itSub('Should connect and send Relay token back', async ({helper}) => {
482 const uniqueApiOptions: ApiOptions = {366 const destination = {
483 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 },
484 };381 };
485382
486 await usingApi(async (api) => {383 const currencies: any = [
487 const destination = {
488 V1: {
489 parents: 1,
490 interior: {X2: [384 [
491 {385 {
492 Parachain: STATEMINE_CHAIN,386 NativeAssetId: 'Parent',
493 },
494 {
495 AccountId32: {
496 network: 'Any',
497 id: bob.addressRaw,
498 },
499 },
500 ]},
501 },387 },
502 };388 50_000_000_000_000_000n,
389 ],
390 ];
503391
504 const currencies: any = [392 const feeItem = 0;
505 [393 const destWeight = 500000000000;
506 {
507 NativeAssetId: 'Parent',
508 },
509 50_000_000_000_000_000n,
510 ],
511 ];
512394
513 const feeItem = 0;395 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, destWeight);
514 const destWeight = 500000000000;
515396
516 const tx = api.tx.xTokens.transferMulticurrencies(currencies, feeItem, destination, destWeight);397 balanceBobFinal = await helper.balance.getSubstrate(bob.address);
517 const events = await executeTransaction(api, bob, tx);
518 const result = getGenericResult(events);
519 expect(result.success).to.be.true;
520
521 [balanceBobFinal] = await getBalance(api, [bob.address]);
522 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);
523
524 }, uniqueApiOptions);
525 });399 });
526
527});400});
528401
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, describeXCM, 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';
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
64describeXCM('[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;
76 let balanceQuartzForeignTokenFinal: bigint;47 let balanceQuartzForeignTokenFinal: bigint;
7748
78 before(async () => {49 before(async () => {
79 console.log('hey babe');50 await usingPlaygrounds(async (helper, privateKey) => {
80 await usingApi(async (api, privateKeyWrapper) => {
81 const keyringSr25519 = new Keyring({type: 'sr25519'});51 alice = await privateKey('//Alice');
82
83 alice = privateKeyWrapper('//Alice');
84 randomAccount = generateKeyringPair(keyringSr25519);52 [randomAccount] = await helper.arrange.createAccounts([0n], alice);
85 });53 });
8654
87 // Karura side55 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
88 await usingApi(
89 async (api) => {
90 const destination = {
91 V0: {
92 X2: [
93 'Parent',
94 {
95 Parachain: QUARTZ_CHAIN,
96 },
97 ],
98 },
99 };
100
101 const metadata = {
102 name: 'QTZ',
103 symbol: 'QTZ',
104 decimals: 18,
105 minimalBalance: 1,
106 };
107
108 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
109 const sudoTx = api.tx.sudo.sudo(tx as any);
110 const events = await submitTransactionAsync(alice, sudoTx);
111 const result = getGenericResult(events);
112 expect(result.success).to.be.true;
113
114 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);
115 const events1 = await submitTransactionAsync(alice, tx1);
116 const result1 = getGenericResult(events1);
117 expect(result1.success).to.be.true;
118
119 [balanceKaruraTokenInit] = await getBalance(api, [randomAccount.address]);
120 {
121 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;
122 balanceQuartzForeignTokenInit = BigInt(free);
123 }
124 },
125 karuraOptions(),
126 );
127
128 // Quartz side
129 await usingApi(async (api) => {
130 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);
131 const events0 = await submitTransactionAsync(alice, tx0);
132 const result0 = getGenericResult(events0);
133 expect(result0.success).to.be.true;
134
135 [balanceQuartzTokenInit] = await getBalance(api, [randomAccount.address]);
136 });
137 });
138
139 it('Should connect and send QTZ to Karura', async () => {
140
141 // Quartz side
142 await usingApi(async (api) => {
143
144 const destination = {56 const destination = {
145 V0: {57 V0: {
146 X2: [58 X2: [
147 'Parent',59 'Parent',
148 {60 {
149 Parachain: KARURA_CHAIN,61 Parachain: QUARTZ_CHAIN,
150 },62 },
151 ],63 ],
152 },64 },
153 };65 };
15466
155 const beneficiary = {67 const metadata = {
156 V0: {68 name: 'QTZ',
157 X1: {69 symbol: 'QTZ',
158 AccountId32: {
159 network: 'Any',
160 id: randomAccount.addressRaw,70 decimals: 18,
161 },71 minimalBalance: 1n,
162 },
163 },
164 };72 };
16573
166 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 = {
167 V1: [88 V0: {
89 X2: [
90 'Parent',
168 {91 {
169 id: {92 Parachain: KARURA_CHAIN,
170 Concrete: {
171 parents: 0,
172 interior: 'Here',
173 },
174 },
175 fun: {
176 Fungible: TRANSFER_AMOUNT,
177 },
178 },93 },
179 ],94 ],
180 };95 },
96 };
18197
182 const feeAssetItem = 0;98 const beneficiary = {
99 V0: {
100 X1: {
101 AccountId32: {
102 network: 'Any',
103 id: randomAccount.addressRaw,
104 },
105 },
106 },
107 };
183108
184 const weightLimit = {109 const assets = {
185 Limited: 5000000000,110 V1: [
111 {
112 id: {
113 Concrete: {
114 parents: 0,
186 };115 interior: 'Here',
116 },
117 },
118 fun: {
119 Fungible: TRANSFER_AMOUNT,
120 },
121 },
122 ],
123 };
187124
188 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);125 const feeAssetItem = 0;
189 const events = await submitTransactionAsync(randomAccount, tx);
190 const result = getGenericResult(events);126 const weightLimit = 5000000000;
191 expect(result.success).to.be.true;
192127
193 [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);
194130
195 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;131 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
196 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));
197 expect(qtzFees > 0n).to.be.true;133 expect(qtzFees > 0n).to.be.true;
198 });
199134
200 // Karura side135 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
201 await usingApi(
202 async (api) => {
203 await waitNewBlocks(api, 3);136 await helper.wait.newBlocks(3);
204 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;137 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
205 balanceQuartzForeignTokenMiddle = BigInt(free);138 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
206139
207 [balanceKaruraTokenMiddle] = await getBalance(api, [randomAccount.address]);140 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;
141 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
208142
209 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;143 console.log(
210 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
211
212 console.log(
213 '[Quartz -> Karura] transaction fees on Karura: %s KAR',144 '[Quartz -> Karura] transaction fees on Karura: %s KAR',
214 bigIntToDecimals(karFees, KARURA_DECIMALS),145 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),
215 );146 );
216 console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));147 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));
217 expect(karFees == 0n).to.be.true;148 expect(karFees == 0n).to.be.true;
218 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;149 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
219 },150 });
220 karuraOptions(),
221 );
222 });151 });
223152
224 it('Should connect to Karura and send QTZ back', async () => {153 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {
225
226 // Karura side154 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
227 await usingApi(
228 async (api) => {
229 const destination = {155 const destination = {
230 V1: {156 V1: {
231 parents: 1,157 parents: 1,
232 interior: {158 interior: {
233 X2: [159 X2: [
234 {Parachain: QUARTZ_CHAIN},160 {Parachain: QUARTZ_CHAIN},
235 {161 {
236 AccountId32: {162 AccountId32: {
237 network: 'Any',163 network: 'Any',
238 id: randomAccount.addressRaw,164 id: randomAccount.addressRaw,
239 },
240 },165 },
241 ],166 },
242 },167 ],
243 },168 },
244 };169 },
170 };
245171
246 const id = {172 const id = {
247 ForeignAsset: 0,173 ForeignAsset: 0,
248 };174 };
249175
250 const destWeight = 50000000;176 const destWeight = 50000000;
251177
252 const tx = api.tx.xTokens.transfer(id as any, TRANSFER_AMOUNT, destination, destWeight);178 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);
253 const events = await submitTransactionAsync(randomAccount, tx);179 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
254 const result = getGenericResult(events);180 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
255 expect(result.success).to.be.true;
256181
257 [balanceKaruraTokenFinal] = await getBalance(api, [randomAccount.address]);182 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;
258 {183 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;
259 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, id)).toJSON() as any;
260 balanceQuartzForeignTokenFinal = BigInt(free);
261 }
262184
263 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;185 console.log(
186 '[Karura -> Quartz] transaction fees on Karura: %s KAR',
264 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;187 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),
188 );
189 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));
265190
266 console.log(191 expect(karFees > 0).to.be.true;
267 '[Karura -> Quartz] transaction fees on Karura: %s KAR',
268 bigIntToDecimals(karFees, KARURA_DECIMALS),192 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
269 );
270 console.log('[Karura -> Quartz] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));193 });
271194
272 expect(karFees > 0).to.be.true;195 await helper.wait.newBlocks(3);
273 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
274 },
275 karuraOptions(),
276 );
277196
278 // Quartz side197 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
279 await usingApi(async (api) => {198 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
280 await waitNewBlocks(api, 3);199 expect(actuallyDelivered > 0).to.be.true;
281200
282 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccount.address]);201 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));
283 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
284 expect(actuallyDelivered > 0).to.be.true;
285202
286 console.log('[Karura -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));203 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
287
288 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
289 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));
290 expect(qtzFees == 0n).to.be.true;205 expect(qtzFees == 0n).to.be.true;
291 });
292 });206 });
293});207});
294208
297 let alice: IKeyringPair;211 let alice: IKeyringPair;
298212
299 before(async () => {213 before(async () => {
300 await usingApi(async (api, privateKeyWrapper) => {214 await usingPlaygrounds(async (_helper, privateKey) => {
301 alice = privateKeyWrapper('//Alice');215 alice = await privateKey('//Alice');
302 });216 });
303 });217 });
304218
305 it('Quartz rejects tokens from the Relay', async () => {219 itSub('Quartz rejects tokens from the Relay', async ({helper}) => {
306 await usingApi(async (api) => {220 await usingRelayPlaygrounds(relayUrl, async (helper) => {
307 const destination = {221 const destination = {
308 V1: {222 V1: {
309 parents: 0,223 parents: 0,
342 };256 };
343257
344 const feeAssetItem = 0;258 const feeAssetItem = 0;
259 const weightLimit = 5_000_000_000;
345260
346 const weightLimit = {261 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
347 Limited: 5_000_000_000,
348 };262 });
349263
350 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);264 const maxWaitBlocks = 3;
351 const events = await submitTransactionAsync(alice, tx);
352 const result = getGenericResult(events);
353 expect(result.success).to.be.true;
354 }, relayOptions());
355265
356 await usingApi(async api => {266 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');
357 const maxWaitBlocks = 3;
358 const dmpQueueExecutedDownward = await waitEvent(
359 api,
360 maxWaitBlocks,
361 'dmpQueue',
362 'ExecutedDownward',
363 );
364267
365 expect(268 expect(
366 dmpQueueExecutedDownward != null,269 dmpQueueExecutedDownward != null,
367 '[Relay] dmpQueue.ExecutedDownward event is expected',270 '[Relay] dmpQueue.ExecutedDownward event is expected',
368 ).to.be.true;271 ).to.be.true;
369272
370 const event = dmpQueueExecutedDownward!.event;273 const event = dmpQueueExecutedDownward!.event;
371 const outcome = event.data[1] as XcmV2TraitsOutcome;274 const outcome = event.data[1] as XcmV2TraitsOutcome;
372275
373 expect(276 expect(
374 outcome.isIncomplete,277 outcome.isIncomplete,
375 '[Relay] The outcome of the XCM should be `Incomplete`',278 '[Relay] The outcome of the XCM should be `Incomplete`',
376 ).to.be.true;279 ).to.be.true;
377280
378 const incomplete = outcome.asIncomplete;281 const incomplete = outcome.asIncomplete;
379 expect(282 expect(
380 incomplete[1].toString() == 'AssetNotFound',283 incomplete[1].toString() == 'AssetNotFound',
381 '[Relay] The XCM error should be `AssetNotFound`',284 '[Relay] The XCM error should be `AssetNotFound`',
382 ).to.be.true;285 ).to.be.true;
383 });
384 });286 });
385287
386 it('Quartz rejects KAR tokens from Karura', async () => {288 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {
387 await usingApi(async (api) => {289 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
388 const destination = {290 const destination = {
389 V1: {291 V1: {
390 parents: 1,292 parents: 1,
408310
409 const destWeight = 50000000;311 const destWeight = 50000000;
410312
411 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);
412 const events = await submitTransactionAsync(alice, tx);314 });
413 const result = getGenericResult(events);
414 expect(result.success).to.be.true;
415 }, karuraOptions());
416315
417 await usingApi(async api => {316 const maxWaitBlocks = 3;
418 const maxWaitBlocks = 3;
419 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');
420317
421 expect(318 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');
422 xcmpQueueFailEvent != null,
423 '[Karura] xcmpQueue.FailEvent event is expected',
424 ).to.be.true;
425319
426 const event = xcmpQueueFailEvent!.event;320 expect(
321 xcmpQueueFailEvent != null,
427 const outcome = event.data[1] as XcmV2TraitsError;322 '[Karura] xcmpQueue.FailEvent event is expected',
323 ).to.be.true;
428324
429 expect(325 const event = xcmpQueueFailEvent!.event;
326 const outcome = event.data[1] as XcmV2TraitsError;
327
328 expect(
430 outcome.isUntrustedReserveLocation,329 outcome.isUntrustedReserveLocation,
431 '[Karura] The XCM error should be `UntrustedReserveLocation`',330 '[Karura] The XCM error should be `UntrustedReserveLocation`',
432 ).to.be.true;331 ).to.be.true;
433 });
434 });332 });
435});333});
436334
437describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {335describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {
438 // Quartz constants336 // Quartz constants
439 let quartzAlice: IKeyringPair;337 let quartzDonor: IKeyringPair;
440 let quartzAssetLocation;338 let quartzAssetLocation;
441339
442 let randomAccountQuartz: IKeyringPair;340 let randomAccountQuartz: IKeyringPair;
445 // Moonriver constants343 // Moonriver constants
446 let assetId: string;344 let assetId: string;
447345
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;346 const councilVotingThreshold = 2;
458 const technicalCommitteeThreshold = 2;347 const technicalCommitteeThreshold = 2;
459 const votingPeriod = 3;348 const votingPeriod = 3;
464 symbol: 'xcQTZ',353 symbol: 'xcQTZ',
465 decimals: 18,354 decimals: 18,
466 isFrozen: false,355 isFrozen: false,
467 minimalBalance: 1,356 minimalBalance: 1n,
468 };357 };
469358
470 let balanceQuartzTokenInit: bigint;359 let balanceQuartzTokenInit: bigint;
478 let balanceMovrTokenFinal: bigint;367 let balanceMovrTokenFinal: bigint;
479368
480 before(async () => {369 before(async () => {
481 await usingApi(async (api, privateKeyWrapper) => {370 await usingPlaygrounds(async (helper, privateKey) => {
482 const keyringEth = new Keyring({type: 'ethereum'});371 quartzDonor = await privateKey('//Alice');
483 const keyringSr25519 = new Keyring({type: 'sr25519'});372 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);
484373
485 quartzAlice = privateKeyWrapper('//Alice');
486 randomAccountQuartz = generateKeyringPair(keyringSr25519);
487 randomAccountMoonriver = generateKeyringPair(keyringEth);
488
489 balanceForeignQtzTokenInit = 0n;374 balanceForeignQtzTokenInit = 0n;
490 });375 });
491376
492 await usingApi(377 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
493 async (api) => {378 const alithAccount = helper.account.alithAccount();
379 const baltatharAccount = helper.account.baltatharAccount();
380 const dorothyAccount = helper.account.dorothyAccount();
494381
495 // >>> Sponsoring Dorothy >>>382 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 <<<
503383
504 const sourceLocation: MultiLocation = api.createType(384 // >>> Sponsoring Dorothy >>>
505 'MultiLocation',385 console.log('Sponsoring Dorothy.......');
506 {386 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
507 parents: 1,387 console.log('Sponsoring Dorothy.......DONE');
508 interior: {X1: {Parachain: QUARTZ_CHAIN}},388 // <<< Sponsoring Dorothy <<<
509 },
510 );
511389
512 quartzAssetLocation = {XCM: sourceLocation};390 quartzAssetLocation = {
391 XCM: {
392 parents: 1,
393 interior: {X1: {Parachain: QUARTZ_CHAIN}},
513 const existentialDeposit = 1;394 },
395 };
396 const existentialDeposit = 1n;
514 const isSufficient = true;397 const isSufficient = true;
515 const unitsPerSecond = '1';398 const unitsPerSecond = 1n;
516 const numAssetsWeightHint = 0;399 const numAssetsWeightHint = 0;
517400
518 const registerTx = api.tx.assetManager.registerForeignAsset(401 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
519 quartzAssetLocation,402 location: quartzAssetLocation,
520 quartzAssetMetadata,403 metadata: quartzAssetMetadata,
521 existentialDeposit,404 existentialDeposit,
522 isSufficient,405 isSufficient,
523 );406 unitsPerSecond,
524 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');407 numAssetsWeightHint,
408 });
409 const proposalHash = blake2AsHex(encodedProposal);
525410
526 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(411 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
527 quartzAssetLocation,
528 unitsPerSecond,
529 numAssetsWeightHint,412 console.log('Encoded length %d', encodedProposal.length);
530 );
531 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');413 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
532414
533 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);415 // >>> Note motion preimage >>>
416 console.log('Note motion preimage.......');
417 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
534 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');418 console.log('Note motion preimage.......DONE');
419 // <<< Note motion preimage <<<
535420
536 // >>> Note motion preimage >>>421 // >>> Propose external motion through council >>>
537 console.log('Note motion preimage.......');422 console.log('Propose external motion through council.......');
538 const encodedProposal = batchCall?.method.toHex() || '';423 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);
539 const proposalHash = blake2AsHex(encodedProposal);424 const encodedMotion = externalMotion?.method.toHex() || '';
540 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);425 const motionHash = blake2AsHex(encodedMotion);
541 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);426 console.log('Motion hash is %s', motionHash);
542 console.log('Encoded length %d', encodedProposal.length);
543427
544 const tx1 = api.tx.democracy.notePreimage(encodedProposal);428 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 <<<
550429
551 // >>> Propose external motion through council >>>430 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(431 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
555 councilVotingThreshold,
556 externalMotion,
557 externalMotion.encodedLength,
558 );
559 const events2 = await submitTransactionAsync(baltatharAccount, tx2);432 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
560 const result2 = getGenericResult(events2);
561 expect(result2.success).to.be.true;
562433
563 const encodedMotion = externalMotion?.method.toHex() || '';434 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);
564 const motionHash = blake2AsHex(encodedMotion);435 console.log('Propose external motion through council.......DONE');
565 console.log('Motion hash is %s', motionHash);436 // <<< Propose external motion through council <<<
566437
567 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);438 // >>> Fast track proposal through technical committee >>>
568 {
569 const events3 = await submitTransactionAsync(dorothyAccount, tx3);439 console.log('Fast track proposal through technical committee.......');
570 const result3 = getGenericResult(events3);440 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
571 expect(result3.success).to.be.true;
572 }441 const encodedFastTrack = fastTrack?.method.toHex() || '';
573 {
574 const events3 = await submitTransactionAsync(baltatharAccount, tx3);
575 const result3 = getGenericResult(events3);442 const fastTrackHash = blake2AsHex(encodedFastTrack);
576 expect(result3.success).to.be.true;443 console.log('FastTrack hash is %s', fastTrackHash);
577 }
578444
579 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);445 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 <<<
585446
586 // >>> Fast track proposal through technical committee >>>447 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(448 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
590 technicalCommitteeThreshold,
591 fastTrack,
592 fastTrack.encodedLength,
593 );
594 const events5 = await submitTransactionAsync(alithAccount, tx5);449 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
595 const result5 = getGenericResult(events5);
596 expect(result5.success).to.be.true;
597450
598 const encodedFastTrack = fastTrack?.method.toHex() || '';451 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);
599 const fastTrackHash = blake2AsHex(encodedFastTrack);452 console.log('Fast track proposal through technical committee.......DONE');
600 console.log('FastTrack hash is %s', fastTrackHash);453 // <<< Fast track proposal through technical committee <<<
601454
602 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;455 // >>> Referendum voting >>>
603 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);456 console.log('Referendum voting.......');
604 {457 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;458 balance: 10_000_000_000_000_000_000n,
608 }459 vote: {aye: true, conviction: 1},
609 {
610 const events6 = await submitTransactionAsync(alithAccount, tx6);
611 const result6 = getGenericResult(events6);460 });
612 expect(result6.success).to.be.true;461 console.log('Referendum voting.......DONE');
613 }462 // <<< Referendum voting <<<
614463
615 const tx7 = api.tx.techCommitteeCollective464 // >>> Acquire Quartz AssetId Info on Moonriver >>>
616 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);
617 const events7 = await submitTransactionAsync(baltatharAccount, tx7);465 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 <<<
622466
623 // >>> Referendum voting >>>467 // 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}}},468 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 <<<
634469
635 // >>> Acquire Quartz AssetId Info on Moonriver >>>470 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();
636 console.log('Acquire Quartz AssetId Info on Moonriver.......');
637471
638 // Wait for the democracy execute472 console.log('QTZ asset ID is %s', assetId);
473 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
639 await waitNewBlocks(api, 5);474 // >>> Acquire Quartz AssetId Info on Moonriver >>>
640475
641 assetId = (await api.query.assetManager.assetTypeId({476 // >>> Sponsoring random Account >>>
477 console.log('Sponsoring random Account.......');
642 XCM: sourceLocation,478 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
643 })).toString();479 console.log('Sponsoring random Account.......DONE');
480 // <<< Sponsoring random Account <<<
644481
645 console.log('QTZ asset ID is %s', assetId);482 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
646 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');
647 // >>> Acquire Quartz AssetId Info on Moonriver >>>483 });
648484
649 // >>> Sponsoring random Account >>>485 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);486 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
665 const events0 = await submitTransactionAsync(quartzAlice, tx0);487 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 });488 });
671 });489 });
672490
673 it('Should connect and send QTZ to Moonriver', async () => {491 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {
674 await usingApi(async (api) => {
675 const currencyId = {492 const currencyId = {
676 NativeAssetId: 'Here',493 NativeAssetId: 'Here',
677 };494 };
678 const dest = {495 const dest = {
679 V1: {496 V1: {
680 parents: 1,497 parents: 1,
681 interior: {498 interior: {
682 X2: [499 X2: [
683 {Parachain: MOONRIVER_CHAIN},500 {Parachain: MOONRIVER_CHAIN},
684 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},501 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},
685 ],502 ],
686 },
687 },503 },
688 };504 },
689 const amount = TRANSFER_AMOUNT;505 };
506 const amount = TRANSFER_AMOUNT;
690 const destWeight = 850000000;507 const destWeight = 850000000;
691508
692 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);509 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;
696510
697 [balanceQuartzTokenMiddle] = await getBalance(api, [randomAccountQuartz.address]);511 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);
698 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;512 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;
699513
700 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;514 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
701 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', bigIntToDecimals(transactionFees));515 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));
702 expect(transactionFees > 0).to.be.true;516 expect(transactionFees > 0).to.be.true;
703 });
704517
705 await usingApi(518 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
706 async (api) => {
707 await waitNewBlocks(api, 3);519 await helper.wait.newBlocks(3);
708520
709 [balanceMovrTokenMiddle] = await getBalance(api, [randomAccountMoonriver.address]);521 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);
710522
711 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;523 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;
712 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',bigIntToDecimals(movrFees));524 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));
713 expect(movrFees == 0n).to.be.true;525 expect(movrFees == 0n).to.be.true;
714526
715 const qtzRandomAccountAsset = (527 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;528 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;
721 console.log('[Quartz -> Moonriver] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));529 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));
722 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;530 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
723 },531 });
724 moonriverOptions(),
725 );
726 });532 });
727533
728 it('Should connect to Moonriver and send QTZ back', async () => {534 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {
729 await usingApi(535 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
730 async (api) => {
731 const asset = {536 const asset = {
732 V1: {537 V1: {
733 id: {538 id: {
734 Concrete: {539 Concrete: {
735 parents: 1,540 parents: 1,
736 interior: {541 interior: {
737 X1: {Parachain: QUARTZ_CHAIN},542 X1: {Parachain: QUARTZ_CHAIN},
738 },
739 },543 },
740 },544 },
741 fun: {
742 Fungible: TRANSFER_AMOUNT,
743 },
744 },545 },
745 };546 fun: {
746 const destination = {
747 V1: {
748 parents: 1,547 Fungible: TRANSFER_AMOUNT,
749 interior: {
750 X2: [
751 {Parachain: QUARTZ_CHAIN},
752 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},
753 ],
754 },
755 },548 },
756 };549 },
550 };
757 const destWeight = 50000000;551 const destination = {
552 V1: {
553 parents: 1,
554 interior: {
555 X2: [
556 {Parachain: QUARTZ_CHAIN},
557 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},
558 ],
559 },
560 },
561 };
562 const destWeight = 50000000;
758563
759 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);564 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;
763565
764 [balanceMovrTokenFinal] = await getBalance(api, [randomAccountMoonriver.address]);566 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);
765567
766 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;568 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;
767 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', bigIntToDecimals(movrFees));569 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));
768 expect(movrFees > 0).to.be.true;570 expect(movrFees > 0).to.be.true;
769571
770 const qtzRandomAccountAsset = (572 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);
771 await api.query.assets.account(assetId, randomAccountMoonriver.address)
772 ).toJSON()! as any;
773573
774 expect(qtzRandomAccountAsset).to.be.null;574 expect(qtzRandomAccountAsset).to.be.null;
775575
776 balanceForeignQtzTokenFinal = 0n;576 balanceForeignQtzTokenFinal = 0n;
777577
778 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;578 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;
779 console.log('[Quartz -> Moonriver] outcome %s QTZ', bigIntToDecimals(qtzOutcomeTransfer));579 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));
780 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;580 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
781 },581 });
782 moonriverOptions(),
783 );
784582
785 await usingApi(async (api) => {583 await helper.wait.newBlocks(3);
786 await waitNewBlocks(api, 3);
787584
788 [balanceQuartzTokenFinal] = await getBalance(api, [randomAccountQuartz.address]);585 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);
789 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;586 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;
790 expect(actuallyDelivered > 0).to.be.true;587 expect(actuallyDelivered > 0).to.be.true;
791588
792 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', bigIntToDecimals(actuallyDelivered));589 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));
793590
794 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;591 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;
795 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', bigIntToDecimals(qtzFees));592 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));
796 expect(qtzFees == 0n).to.be.true;593 expect(qtzFees == 0n).to.be.true;
797 });
798 });594 });
799});595});
800596
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, describeXCM, 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';
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
64describeXCM('[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;
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 = await 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
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 = await 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
436describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {339describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {
437
438 // Unique constants340 // Unique constants
439 let uniqueAlice: IKeyringPair;341 let uniqueDonor: IKeyringPair;
440 let uniqueAssetLocation;342 let uniqueAssetLocation;
441343
442 let randomAccountUnique: IKeyringPair;344 let randomAccountUnique: IKeyringPair;
445 // Moonbeam constants347 // Moonbeam constants
446 let assetId: string;348 let assetId: string;
447349
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;350 const councilVotingThreshold = 2;
458 const technicalCommitteeThreshold = 2;351 const technicalCommitteeThreshold = 2;
459 const votingPeriod = 3;352 const votingPeriod = 3;
464 symbol: 'xcUNQ',357 symbol: 'xcUNQ',
465 decimals: 18,358 decimals: 18,
466 isFrozen: false,359 isFrozen: false,
467 minimalBalance: 1,360 minimalBalance: 1n,
468 };361 };
469362
470 let balanceUniqueTokenInit: bigint;363 let balanceUniqueTokenInit: bigint;
478 let balanceGlmrTokenFinal: bigint;371 let balanceGlmrTokenFinal: bigint;
479372
480 before(async () => {373 before(async () => {
481 await usingApi(async (api, privateKeyWrapper) => {374 await usingPlaygrounds(async (helper, privateKey) => {
482 const keyringEth = new Keyring({type: 'ethereum'});375 uniqueDonor = await privateKey('//Alice');
483 const keyringSr25519 = new Keyring({type: 'sr25519'});376 [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);
484377
485 uniqueAlice = privateKeyWrapper('//Alice');
486 randomAccountUnique = generateKeyringPair(keyringSr25519);
487 randomAccountMoonbeam = generateKeyringPair(keyringEth);
488
489 balanceForeignUnqTokenInit = 0n;378 balanceForeignUnqTokenInit = 0n;
490 });379 });
491380
492 await usingApi(381 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
493 async (api) => {382 const alithAccount = helper.account.alithAccount();
383 const baltatharAccount = helper.account.baltatharAccount();
384 const dorothyAccount = helper.account.dorothyAccount();
494385
495 // >>> Sponsoring Dorothy >>>386 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 <<<
503387
504 const sourceLocation: MultiLocation = api.createType(388 // >>> Sponsoring Dorothy >>>
505 'MultiLocation',389 console.log('Sponsoring Dorothy.......');
506 {390 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);
507 parents: 1,391 console.log('Sponsoring Dorothy.......DONE');
508 interior: {X1: {Parachain: UNIQUE_CHAIN}},392 // <<< Sponsoring Dorothy <<<
509 },
510 );
511393
512 uniqueAssetLocation = {XCM: sourceLocation};394 uniqueAssetLocation = {
395 XCM: {
396 parents: 1,
397 interior: {X1: {Parachain: UNIQUE_CHAIN}},
513 const existentialDeposit = 1;398 },
399 };
400 const existentialDeposit = 1n;
514 const isSufficient = true;401 const isSufficient = true;
515 const unitsPerSecond = '1';402 const unitsPerSecond = 1n;
516 const numAssetsWeightHint = 0;403 const numAssetsWeightHint = 0;
517404
518 const registerTx = api.tx.assetManager.registerForeignAsset(405 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
519 uniqueAssetLocation,406 location: uniqueAssetLocation,
520 uniqueAssetMetadata,407 metadata: uniqueAssetMetadata,
521 existentialDeposit,408 existentialDeposit,
522 isSufficient,409 isSufficient,
523 );410 unitsPerSecond,
524 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');411 numAssetsWeightHint,
412 });
413 const proposalHash = blake2AsHex(encodedProposal);
525414
526 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(415 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
527 uniqueAssetLocation,
528 unitsPerSecond,
529 numAssetsWeightHint,416 console.log('Encoded length %d', encodedProposal.length);
530 );
531 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');417 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
532418
533 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);419 // >>> Note motion preimage >>>
420 console.log('Note motion preimage.......');
421 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
534 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');422 console.log('Note motion preimage.......DONE');
423 // <<< Note motion preimage <<<
535424
536 // >>> Note motion preimage >>>425 // >>> Propose external motion through council >>>
537 console.log('Note motion preimage.......');426 console.log('Propose external motion through council.......');
538 const encodedProposal = batchCall?.method.toHex() || '';427 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);
539 const proposalHash = blake2AsHex(encodedProposal);428 const encodedMotion = externalMotion?.method.toHex() || '';
540 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);429 const motionHash = blake2AsHex(encodedMotion);
541 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);430 console.log('Motion hash is %s', motionHash);
542 console.log('Encoded length %d', encodedProposal.length);
543431
544 const tx1 = api.tx.democracy.notePreimage(encodedProposal);432 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 <<<
550433
551 // >>> Propose external motion through council >>>434 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(435 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
555 councilVotingThreshold,
556 externalMotion,
557 externalMotion.encodedLength,
558 );
559 const events2 = await submitTransactionAsync(baltatharAccount, tx2);436 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
560 const result2 = getGenericResult(events2);
561 expect(result2.success).to.be.true;
562437
563 const encodedMotion = externalMotion?.method.toHex() || '';438 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);
564 const motionHash = blake2AsHex(encodedMotion);439 console.log('Propose external motion through council.......DONE');
565 console.log('Motion hash is %s', motionHash);440 // <<< Propose external motion through council <<<
566441
567 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);442 // >>> Fast track proposal through technical committee >>>
568 {
569 const events3 = await submitTransactionAsync(dorothyAccount, tx3);443 console.log('Fast track proposal through technical committee.......');
570 const result3 = getGenericResult(events3);444 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
571 expect(result3.success).to.be.true;
572 }445 const encodedFastTrack = fastTrack?.method.toHex() || '';
573 {
574 const events3 = await submitTransactionAsync(baltatharAccount, tx3);
575 const result3 = getGenericResult(events3);446 const fastTrackHash = blake2AsHex(encodedFastTrack);
576 expect(result3.success).to.be.true;447 console.log('FastTrack hash is %s', fastTrackHash);
577 }
578448
579 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);449 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 <<<
585450
586 // >>> Fast track proposal through technical committee >>>451 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(452 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
590 technicalCommitteeThreshold,
591 fastTrack,
592 fastTrack.encodedLength,
593 );
594 const events5 = await submitTransactionAsync(alithAccount, tx5);453 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
595 const result5 = getGenericResult(events5);
596 expect(result5.success).to.be.true;
597454
598 const encodedFastTrack = fastTrack?.method.toHex() || '';455 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);
599 const fastTrackHash = blake2AsHex(encodedFastTrack);456 console.log('Fast track proposal through technical committee.......DONE');
600 console.log('FastTrack hash is %s', fastTrackHash);457 // <<< Fast track proposal through technical committee <<<
601458
602 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;459 // >>> Referendum voting >>>
603 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);460 console.log('Referendum voting.......');
604 {461 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;462 balance: 10_000_000_000_000_000_000n,
608 }463 vote: {aye: true, conviction: 1},
609 {
610 const events6 = await submitTransactionAsync(alithAccount, tx6);
611 const result6 = getGenericResult(events6);464 });
612 expect(result6.success).to.be.true;465 console.log('Referendum voting.......DONE');
613 }466 // <<< Referendum voting <<<
614467
615 const tx7 = api.tx.techCommitteeCollective468 // >>> Acquire Unique AssetId Info on Moonbeam >>>
616 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);
617 const events7 = await submitTransactionAsync(baltatharAccount, tx7);469 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 <<<
622470
623 // >>> Referendum voting >>>471 // 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}}},472 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 <<<
634473
635 // >>> Acquire Unique AssetId Info on Moonbeam >>>474 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
636 console.log('Acquire Unique AssetId Info on Moonbeam.......');
637475
638 // Wait for the democracy execute476 console.log('UNQ asset ID is %s', assetId);
477 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
639 await waitNewBlocks(api, 5);478 // >>> Acquire Unique AssetId Info on Moonbeam >>>
640479
641 assetId = (await api.query.assetManager.assetTypeId({480 // >>> Sponsoring random Account >>>
481 console.log('Sponsoring random Account.......');
642 XCM: sourceLocation,482 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);
643 })).toString();483 console.log('Sponsoring random Account.......DONE');
484 // <<< Sponsoring random Account <<<
644485
645 console.log('UNQ asset ID is %s', assetId);486 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);
646 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');
647 // >>> Acquire Unique AssetId Info on Moonbeam >>>487 });
648488
649 // >>> Sponsoring random Account >>>489 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);490 await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);
665 const events0 = await submitTransactionAsync(uniqueAlice, tx0);491 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 });492 });
671 });493 });
672494
673 it('Should connect and send UNQ to Moonbeam', async () => {495 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {
674 await usingApi(async (api) => {
675 const currencyId = {496 const currencyId = {
676 NativeAssetId: 'Here',497 NativeAssetId: 'Here',
677 };498 };
678 const dest = {499 const dest = {
679 V1: {500 V1: {
680 parents: 1,501 parents: 1,
681 interior: {502 interior: {
682 X2: [503 X2: [
683 {Parachain: MOONBEAM_CHAIN},504 {Parachain: MOONBEAM_CHAIN},
684 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},505 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},
685 ],506 ],
686 },
687 },507 },
688 };508 },
689 const amount = TRANSFER_AMOUNT;509 };
510 const amount = TRANSFER_AMOUNT;
690 const destWeight = 850000000;511 const destWeight = 850000000;
691512
692 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);513 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;
696514
697 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);515 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);
698 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;516 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;
699517
700 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;518 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
701 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));519 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));
702 expect(transactionFees > 0).to.be.true;520 expect(transactionFees > 0).to.be.true;
703 });
704521
705 await usingApi(522 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
706 async (api) => {
707 await waitNewBlocks(api, 3);523 await helper.wait.newBlocks(3);
708524
709 [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);525 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);
710526
711 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;527 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;
712 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));528 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));
713 expect(glmrFees == 0n).to.be.true;529 expect(glmrFees == 0n).to.be.true;
714530
715 const unqRandomAccountAsset = (531 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;
716 await api.query.assets.account(assetId, randomAccountMoonbeam.address)
717 ).toJSON()! as any;
718532
719 balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);533 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;
720 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;
721 console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));534 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));
722 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;535 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;
723 },536 });
724 moonbeamOptions(),
725 );
726 });537 });
727538
728 it('Should connect to Moonbeam and send UNQ back', async () => {539 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {
729 await usingApi(540 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
730 async (api) => {
731 const asset = {541 const asset = {
732 V1: {542 V1: {
733 id: {543 id: {
734 Concrete: {544 Concrete: {
735 parents: 1,545 parents: 1,
736 interior: {546 interior: {
737 X1: {Parachain: UNIQUE_CHAIN},547 X1: {Parachain: UNIQUE_CHAIN},
738 },
739 },548 },
740 },549 },
741 fun: {
742 Fungible: TRANSFER_AMOUNT,
743 },
744 },550 },
745 };551 fun: {
746 const destination = {
747 V1: {
748 parents: 1,552 Fungible: TRANSFER_AMOUNT,
749 interior: {
750 X2: [
751 {Parachain: UNIQUE_CHAIN},
752 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},
753 ],
754 },
755 },553 },
756 };554 },
555 };
757 const destWeight = 50000000;556 const destination = {
557 V1: {
558 parents: 1,
559 interior: {
560 X2: [
561 {Parachain: UNIQUE_CHAIN},
562 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},
563 ],
564 },
565 },
566 };
567 const destWeight = 50000000;
758568
759 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);569 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;
763570
764 [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);571 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);
765572
766 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;573 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;
767 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));574 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));
768 expect(glmrFees > 0).to.be.true;575 expect(glmrFees > 0).to.be.true;
769576
770 const unqRandomAccountAsset = (577 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);
771 await api.query.assets.account(assetId, randomAccountMoonbeam.address)
772 ).toJSON()! as any;
773578
774 expect(unqRandomAccountAsset).to.be.null;579 expect(unqRandomAccountAsset).to.be.null;
580
581 balanceForeignUnqTokenFinal = 0n;
775582
776 balanceForeignUnqTokenFinal = 0n;583 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;
584 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
585 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
586 });
777587
778 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;588 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 );
784589
785 await usingApi(async (api) => {590 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);
786 await waitNewBlocks(api, 3);591 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;
592 expect(actuallyDelivered > 0).to.be.true;
787593
788 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);594 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));
789 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;
790 expect(actuallyDelivered > 0).to.be.true;
791595
792 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));596 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));597 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
796 expect(unqFees == 0n).to.be.true;598 expect(unqFees == 0n).to.be.true;
797 });
798 });599 });
799});600});
800601