difftreelog
test(xcm) qtz/unq rejects relay tokens
in: master
3 files changed
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -104,14 +104,9 @@
}
export function bigIntToDecimals(number: bigint, decimals = 18): string {
- let numberStr = number.toString();
- console.log('[0] str = ', numberStr);
-
- // Get rid of `n` at the end
- numberStr = numberStr.substring(0, numberStr.length - 1);
- console.log('[1] str = ', numberStr);
-
+ const numberStr = number.toString();
const dotPos = numberStr.length - decimals;
+
if (dotPos <= 0) {
return '0.' + numberStr;
} else {
@@ -1794,19 +1789,31 @@
): Promise<EventRecord | null> {
const promise = new Promise<EventRecord | null>(async (resolve) => {
- const unsubscribe = await api.query.system.events(eventRecords => {
+ const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => {
+ const blockNumber = header.number.toHuman();
+ const blockHash = header.hash;
+ const eventIdStr = `${eventSection}.${eventMethod}`;
+ const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
+
+ console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
+
+ const apiAt = await api.at(blockHash);
+ const eventRecords = await apiAt.query.system.events();
+
const neededEvent = eventRecords.find(r => {
return r.event.section == eventSection && r.event.method == eventMethod;
});
if (neededEvent) {
+ console.log(`Event \`${eventIdStr}\` is found`);
+
unsubscribe();
resolve(neededEvent);
- }
-
- if (maxBlocksToWait > 0) {
+ } else if (maxBlocksToWait > 0) {
maxBlocksToWait--;
} else {
+ console.log(`Event \`${eventIdStr}\` is NOT found`);
+
unsubscribe();
resolve(null);
}
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -26,6 +26,7 @@
import {blake2AsHex} from '@polkadot/util-crypto';
import waitNewBlocks from '../substrate/wait-new-blocks';
import getBalance from '../substrate/get-balance';
+import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -34,6 +35,7 @@
const KARURA_CHAIN = 2000;
const MOONRIVER_CHAIN = 2023;
+const RELAY_PORT = 9844;
const KARURA_PORT = 9946;
const MOONRIVER_PORT = 9947;
@@ -55,6 +57,10 @@
return parachainApiOptions(MOONRIVER_PORT);
}
+function relayOptions(): ApiOptions {
+ return parachainApiOptions(RELAY_PORT);
+}
+
describe_xcm('Integration test: Exchanging tokens with Karura', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
@@ -200,8 +206,8 @@
const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;
const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;
- console.log('
- [Quartz -> Karura] transaction fees on Karura: %s KAR',
+ console.log(
+ '[Quartz -> Karura] transaction fees on Karura: %s KAR',
bigIntToDecimals(karFees, KARURA_DECIMALS),
);
console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer));
@@ -281,10 +287,100 @@
expect(qtzFees == 0n).to.be.true;
});
});
+});
+
+// These tests are relevant only when the foreign asset pallet is disabled
+describe('Integration test: Quartz rejects non-native tokens', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ });
+ });
+
+ it('Quartz rejects tokens from the Relay', async () => {
+ await usingApi(async (api) => {
+ const destination = {
+ V1: {
+ parents: 0,
+ interior: {X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
+ },
+ }};
+
+ const beneficiary = {
+ V1: {
+ parents: 0,
+ interior: {X1: {
+ AccountId32: {
+ network: 'Any',
+ id: alice.addressRaw,
+ },
+ }},
+ },
+ };
+
+ const assets = {
+ V1: [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ fun: {
+ Fungible: 50_000_000_000_000_000n,
+ },
+ },
+ ],
+ };
+
+ const feeAssetItem = 0;
+
+ const weightLimit = {
+ Limited: 5_000_000_000,
+ };
+
+ const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
+ const events = await submitTransactionAsync(alice, tx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+ }, relayOptions());
+
+ await usingApi(async api => {
+ const maxWaitBlocks = 3;
+ const dmpQueueExecutedDownward = await waitEvent(
+ api,
+ maxWaitBlocks,
+ 'dmpQueue',
+ 'ExecutedDownward',
+ );
+
+ expect(
+ dmpQueueExecutedDownward != null,
+ '[Relay] dmpQueue.ExecutedDownward event is expected',
+ ).to.be.true;
+
+ const event = dmpQueueExecutedDownward!.event;
+ const outcome = event.data[1] as XcmV2TraitsOutcome;
+
+ expect(
+ outcome.isIncomplete,
+ '[Relay] The outcome of the XCM should be `Incomplete`',
+ ).to.be.true;
+
+ const incomplete = outcome.asIncomplete;
+ expect(
+ incomplete[1].toString() == 'AssetNotFound',
+ '[Relay] The XCM error should be `AssetNotFound`',
+ ).to.be.true;
+ });
+ });
it('Quartz rejects KAR tokens from Karura', async () => {
- // This test is relevant only when the foreign asset pallet is disabled
-
await usingApi(async (api) => {
const destination = {
V1: {
@@ -295,7 +391,7 @@
{
AccountId32: {
network: 'Any',
- id: randomAccount.addressRaw,
+ id: alice.addressRaw,
},
},
],
@@ -321,7 +417,15 @@
expect(
xcmpQueueFailEvent != null,
- 'Only native token is supported when the Foreign-Assets pallet is not connected',
+ '[Karura] xcmpQueue.FailEvent event is expected',
+ ).to.be.true;
+
+ const event = xcmpQueueFailEvent!.event;
+ const outcome = event.data[1] as XcmV2TraitsError;
+
+ expect(
+ outcome.isUntrustedReserveLocation,
+ '[Karura] The XCM error should be `UntrustedReserveLocation`',
).to.be.true;
});
});
tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai from 'chai';18import chaiAsPromised from 'chai-as-promised';1920import {WsProvider, Keyring} from '@polkadot/api';21import {ApiOptions} from '@polkadot/api/types';22import {IKeyringPair} from '@polkadot/types/types';23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers';25import {MultiLocation} from '@polkadot/types/interfaces';26import {blake2AsHex} from '@polkadot/util-crypto';27import waitNewBlocks from '../substrate/wait-new-blocks';28import getBalance from '../substrate/get-balance';2930chai.use(chaiAsPromised);31const expect = chai.expect;3233const UNIQUE_CHAIN = 2037;34const ACALA_CHAIN = 2000;35const MOONBEAM_CHAIN = 2004;3637const ACALA_PORT = 9946;38const MOONBEAM_PORT = 9947;3940const ACALA_DECIMALS = 12;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344function parachainApiOptions(port: number): ApiOptions {45 return {46 provider: new WsProvider('ws://127.0.0.1:' + port.toString()),47 };48}4950function acalaOptions(): ApiOptions {51 return parachainApiOptions(ACALA_PORT);52}5354function moonbeamOptions(): ApiOptions {55 return parachainApiOptions(MOONBEAM_PORT);56}5758describe_xcm('Integration test: Exchanging tokens with Acala', () => {59 let alice: IKeyringPair;60 let randomAccount: IKeyringPair;61 62 let balanceUniqueTokenInit: bigint;63 let balanceUniqueTokenMiddle: bigint;64 let balanceUniqueTokenFinal: bigint;65 let balanceAcalaTokenInit: bigint;66 let balanceAcalaTokenMiddle: bigint;67 let balanceAcalaTokenFinal: bigint;68 let balanceUniqueForeignTokenInit: bigint;69 let balanceUniqueForeignTokenMiddle: bigint;70 let balanceUniqueForeignTokenFinal: bigint;71 72 before(async () => {73 await usingApi(async (api, privateKeyWrapper) => {74 alice = privateKeyWrapper('//Alice');75 randomAccount = generateKeyringPair();76 });7778 // Acala side79 await usingApi(80 async (api) => {81 const destination = {82 V0: {83 X2: [84 'Parent',85 {86 Parachain: UNIQUE_CHAIN,87 },88 ],89 },90 };91 92 const metadata = {93 name: 'UNQ',94 symbol: 'UNQ',95 decimals: 18,96 minimalBalance: 1,97 };98 99 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);100 const sudoTx = api.tx.sudo.sudo(tx as any);101 const events = await submitTransactionAsync(alice, sudoTx);102 const result = getGenericResult(events);103 expect(result.success).to.be.true;104 105 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);106 const events1 = await submitTransactionAsync(alice, tx1);107 const result1 = getGenericResult(events1);108 expect(result1.success).to.be.true;109 110 [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]);111 {112 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;113 balanceUniqueForeignTokenInit = BigInt(free);114 }115 },116 acalaOptions(),117 );118 119 // Unique side120 await usingApi(async (api) => {121 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);122 const events0 = await submitTransactionAsync(alice, tx0);123 const result0 = getGenericResult(events0);124 expect(result0.success).to.be.true;125 126 [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]);127 });128 });129 130 it('Should connect and send UNQ to Acala', async () => {131 132 // Unique side133 await usingApi(async (api) => {134 135 const destination = {136 V0: {137 X2: [138 'Parent',139 {140 Parachain: ACALA_CHAIN,141 },142 ],143 },144 };145 146 const beneficiary = {147 V0: {148 X1: {149 AccountId32: {150 network: 'Any',151 id: randomAccount.addressRaw,152 },153 },154 },155 };156 157 const assets = {158 V1: [159 {160 id: {161 Concrete: {162 parents: 0,163 interior: 'Here',164 },165 },166 fun: {167 Fungible: TRANSFER_AMOUNT,168 },169 },170 ],171 };172 173 const feeAssetItem = 0;174 175 const weightLimit = {176 Limited: 5000000000,177 };178 179 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);180 const events = await submitTransactionAsync(randomAccount, tx);181 const result = getGenericResult(events);182 expect(result.success).to.be.true;183 184 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]);185 186 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;187 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));188 expect(unqFees > 0n).to.be.true;189 });190 191 // Acala side192 await usingApi(193 async (api) => {194 await waitNewBlocks(api, 3);195 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;196 balanceUniqueForeignTokenMiddle = BigInt(free);197 198 [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]);199200 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;201 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;202203 console.log(204 '[Unique -> Acala] transaction fees on Acala: %s ACA',205 bigIntToDecimals(acaFees, ACALA_DECIMALS),206 );207 console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));208 expect(acaFees == 0n).to.be.true;209 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;210 },211 acalaOptions(),212 );213 });214 215 it('Should connect to Acala and send UNQ back', async () => {216 217 // Acala side218 await usingApi(219 async (api) => {220 const destination = {221 V1: {222 parents: 1,223 interior: {224 X2: [225 {Parachain: UNIQUE_CHAIN},226 {227 AccountId32: {228 network: 'Any',229 id: randomAccount.addressRaw,230 },231 },232 ],233 },234 },235 };236 237 const id = {238 ForeignAsset: 0,239 };240241 const destWeight = 50000000;242 243 const tx = api.tx.xTokens.transfer(id, TRANSFER_AMOUNT, destination, destWeight);244 const events = await submitTransactionAsync(randomAccount, tx);245 const result = getGenericResult(events);246 expect(result.success).to.be.true;247 248 [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]);249 {250 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;251 balanceUniqueForeignTokenFinal = BigInt(free);252 }253 254 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;255 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;256257 console.log(258 '[Acala -> Unique] transaction fees on Acala: %s ACA',259 bigIntToDecimals(acaFees, ACALA_DECIMALS),260 );261 console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));262263 expect(acaFees > 0).to.be.true;264 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;265 },266 acalaOptions(),267 );268269 // Unique side270 await usingApi(async (api) => {271 await waitNewBlocks(api, 3);272 273 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]);274 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;275 expect(actuallyDelivered > 0).to.be.true;276277 console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));278 279 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;280 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));281 expect(unqFees == 0n).to.be.true;282 });283 });284285 it('Unique rejects ACA tokens from Acala', async () => {286 // This test is relevant only when the foreign asset pallet is disabled287288 await usingApi(async (api) => {289 const destination = {290 V1: {291 parents: 1,292 interior: {293 X2: [294 {Parachain: UNIQUE_CHAIN},295 {296 AccountId32: {297 network: 'Any',298 id: randomAccount.addressRaw,299 },300 },301 ],302 },303 },304 };305306 const id = {307 Token: 'ACA',308 };309310 const destWeight = 50000000;311312 const tx = api.tx.xTokens.transfer(id, 100_000_000_000, destination, destWeight);313 const events = await submitTransactionAsync(alice, tx);314 const result = getGenericResult(events);315 expect(result.success).to.be.true;316 }, acalaOptions());317318 await usingApi(async api => {319 const maxWaitBlocks = 3;320 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');321322 expect(323 xcmpQueueFailEvent != null,324 'Only native token is supported when the Foreign-Assets pallet is not connected',325 ).to.be.true;326 });327 });328});329330describe_xcm('Integration test: Exchanging UNQ with Moonbeam', () => {331332 // Unique constants333 let uniqueAlice: IKeyringPair;334 let uniqueAssetLocation;335336 let randomAccountUnique: IKeyringPair;337 let randomAccountMoonbeam: IKeyringPair;338339 // Moonbeam constants340 let assetId: string;341342 const moonbeamKeyring = new Keyring({type: 'ethereum'});343 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';344 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';345 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';346347 const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');348 const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');349 const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');350351 const councilVotingThreshold = 2;352 const technicalCommitteeThreshold = 2;353 const votingPeriod = 3;354 const delayPeriod = 0;355356 const uniqueAssetMetadata = {357 name: 'xcUnique',358 symbol: 'xcUNQ',359 decimals: 18,360 isFrozen: false,361 minimalBalance: 1,362 };363364 let balanceUniqueTokenInit: bigint;365 let balanceUniqueTokenMiddle: bigint;366 let balanceUniqueTokenFinal: bigint;367 let balanceForeignUnqTokenInit: bigint;368 let balanceForeignUnqTokenMiddle: bigint;369 let balanceForeignUnqTokenFinal: bigint;370 let balanceGlmrTokenInit: bigint;371 let balanceGlmrTokenMiddle: bigint;372 let balanceGlmrTokenFinal: bigint;373374 before(async () => {375 await usingApi(async (api, privateKeyWrapper) => {376 uniqueAlice = privateKeyWrapper('//Alice');377 randomAccountUnique = generateKeyringPair();378 randomAccountMoonbeam = generateKeyringPair('ethereum');379380 balanceForeignUnqTokenInit = 0n;381 });382383 await usingApi(384 async (api) => {385386 // >>> Sponsoring Dorothy >>>387 console.log('Sponsoring Dorothy.......');388 const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);389 const events0 = await submitTransactionAsync(alithAccount, tx0);390 const result0 = getGenericResult(events0);391 expect(result0.success).to.be.true;392 console.log('Sponsoring Dorothy.......DONE');393 // <<< Sponsoring Dorothy <<<394395 const sourceLocation: MultiLocation = api.createType(396 'MultiLocation',397 {398 parents: 1,399 interior: {X1: {Parachain: UNIQUE_CHAIN}},400 },401 );402403 uniqueAssetLocation = {XCM: sourceLocation};404 const existentialDeposit = 1;405 const isSufficient = true;406 const unitsPerSecond = '1';407 const numAssetsWeightHint = 0;408409 const registerTx = api.tx.assetManager.registerForeignAsset(410 uniqueAssetLocation,411 uniqueAssetMetadata,412 existentialDeposit,413 isSufficient,414 );415 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');416417 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(418 uniqueAssetLocation,419 unitsPerSecond,420 numAssetsWeightHint,421 );422 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');423424 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);425 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');426427 // >>> Note motion preimage >>>428 console.log('Note motion preimage.......');429 const encodedProposal = batchCall?.method.toHex() || '';430 const proposalHash = blake2AsHex(encodedProposal);431 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);432 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);433 console.log('Encoded length %d', encodedProposal.length);434435 const tx1 = api.tx.democracy.notePreimage(encodedProposal);436 const events1 = await submitTransactionAsync(baltatharAccount, tx1);437 const result1 = getGenericResult(events1);438 expect(result1.success).to.be.true;439 console.log('Note motion preimage.......DONE');440 // <<< Note motion preimage <<<441442 // >>> Propose external motion through council >>>443 console.log('Propose external motion through council.......');444 const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);445 const tx2 = api.tx.councilCollective.propose(446 councilVotingThreshold,447 externalMotion,448 externalMotion.encodedLength,449 );450 const events2 = await submitTransactionAsync(baltatharAccount, tx2);451 const result2 = getGenericResult(events2);452 expect(result2.success).to.be.true;453454 const encodedMotion = externalMotion?.method.toHex() || '';455 const motionHash = blake2AsHex(encodedMotion);456 console.log('Motion hash is %s', motionHash);457458 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);459 {460 const events3 = await submitTransactionAsync(dorothyAccount, tx3);461 const result3 = getGenericResult(events3);462 expect(result3.success).to.be.true;463 }464 {465 const events3 = await submitTransactionAsync(baltatharAccount, tx3);466 const result3 = getGenericResult(events3);467 expect(result3.success).to.be.true;468 }469470 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);471 const events4 = await submitTransactionAsync(dorothyAccount, tx4);472 const result4 = getGenericResult(events4);473 expect(result4.success).to.be.true;474 console.log('Propose external motion through council.......DONE');475 // <<< Propose external motion through council <<<476477 // >>> Fast track proposal through technical committee >>>478 console.log('Fast track proposal through technical committee.......');479 const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);480 const tx5 = api.tx.techCommitteeCollective.propose(481 technicalCommitteeThreshold,482 fastTrack,483 fastTrack.encodedLength,484 );485 const events5 = await submitTransactionAsync(alithAccount, tx5);486 const result5 = getGenericResult(events5);487 expect(result5.success).to.be.true;488489 const encodedFastTrack = fastTrack?.method.toHex() || '';490 const fastTrackHash = blake2AsHex(encodedFastTrack);491 console.log('FastTrack hash is %s', fastTrackHash);492493 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;494 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);495 {496 const events6 = await submitTransactionAsync(baltatharAccount, tx6);497 const result6 = getGenericResult(events6);498 expect(result6.success).to.be.true;499 }500 {501 const events6 = await submitTransactionAsync(alithAccount, tx6);502 const result6 = getGenericResult(events6);503 expect(result6.success).to.be.true;504 }505506 const tx7 = api.tx.techCommitteeCollective507 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);508 const events7 = await submitTransactionAsync(baltatharAccount, tx7);509 const result7 = getGenericResult(events7);510 expect(result7.success).to.be.true;511 console.log('Fast track proposal through technical committee.......DONE');512 // <<< Fast track proposal through technical committee <<<513514 // >>> Referendum voting >>>515 console.log('Referendum voting.......');516 const tx8 = api.tx.democracy.vote(517 0,518 {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},519 );520 const events8 = await submitTransactionAsync(dorothyAccount, tx8);521 const result8 = getGenericResult(events8);522 expect(result8.success).to.be.true;523 console.log('Referendum voting.......DONE');524 // <<< Referendum voting <<<525526 // >>> Acquire Unique AssetId Info on Moonbeam >>>527 console.log('Acquire Unique AssetId Info on Moonbeam.......');528529 // Wait for the democracy execute530 await waitNewBlocks(api, 5);531532 assetId = (await api.query.assetManager.assetTypeId({533 XCM: sourceLocation,534 })).toString();535536 console.log('UNQ asset ID is %s', assetId);537 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');538 // >>> Acquire Unique AssetId Info on Moonbeam >>>539540 // >>> Sponsoring random Account >>>541 console.log('Sponsoring random Account.......');542 const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);543 const events10 = await submitTransactionAsync(baltatharAccount, tx10);544 const result10 = getGenericResult(events10);545 expect(result10.success).to.be.true;546 console.log('Sponsoring random Account.......DONE');547 // <<< Sponsoring random Account <<<548549 [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]);550 },551 moonbeamOptions(),552 );553554 await usingApi(async (api) => {555 const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT);556 const events0 = await submitTransactionAsync(uniqueAlice, tx0);557 const result0 = getGenericResult(events0);558 expect(result0.success).to.be.true;559560 [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]);561 });562 });563564 it('Should connect and send UNQ to Moonbeam', async () => {565 await usingApi(async (api) => {566 const currencyId = {567 NativeAssetId: 'Here',568 };569 const dest = {570 V1: {571 parents: 1,572 interior: {573 X2: [574 {Parachain: MOONBEAM_CHAIN},575 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},576 ],577 },578 },579 };580 const amount = TRANSFER_AMOUNT;581 const destWeight = 850000000;582583 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);584 const events = await submitTransactionAsync(randomAccountUnique, tx);585 const result = getGenericResult(events);586 expect(result.success).to.be.true;587588 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);589 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;590591 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;592 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));593 expect(transactionFees > 0).to.be.true;594 });595596 await usingApi(597 async (api) => {598 await waitNewBlocks(api, 3);599600 [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);601602 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;603 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));604 expect(glmrFees == 0n).to.be.true;605606 const unqRandomAccountAsset = (607 await api.query.assets.account(assetId, randomAccountMoonbeam.address)608 ).toJSON()! as any;609610 balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);611 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;612 console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));613 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;614 },615 moonbeamOptions(),616 );617 });618619 it('Should connect to Moonbeam and send UNQ back', async () => {620 await usingApi(621 async (api) => {622 const asset = {623 V1: {624 id: {625 Concrete: {626 parents: 1,627 interior: {628 X1: {Parachain: UNIQUE_CHAIN},629 },630 },631 },632 fun: {633 Fungible: TRANSFER_AMOUNT,634 },635 },636 };637 const destination = {638 V1: {639 parents: 1,640 interior: {641 X2: [642 {Parachain: UNIQUE_CHAIN},643 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},644 ],645 },646 },647 };648 const destWeight = 50000000;649650 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);651 const events = await submitTransactionAsync(randomAccountMoonbeam, tx);652 const result = getGenericResult(events);653 expect(result.success).to.be.true;654655 [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);656657 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;658 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));659 expect(glmrFees > 0).to.be.true;660661 const unqRandomAccountAsset = (662 await api.query.assets.account(assetId, randomAccountMoonbeam.address)663 ).toJSON()! as any;664665 expect(unqRandomAccountAsset).to.be.null;666667 balanceForeignUnqTokenFinal = 0n;668669 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;670 console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));671 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;672 },673 moonbeamOptions(),674 );675676 await usingApi(async (api) => {677 await waitNewBlocks(api, 3);678679 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);680 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;681 expect(actuallyDelivered > 0).to.be.true;682683 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));684685 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;686 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));687 expect(unqFees == 0n).to.be.true;688 });689 });690});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai from 'chai';18import chaiAsPromised from 'chai-as-promised';1920import {WsProvider, Keyring} from '@polkadot/api';21import {ApiOptions} from '@polkadot/api/types';22import {IKeyringPair} from '@polkadot/types/types';23import usingApi, {submitTransactionAsync} from '../substrate/substrate-api';24import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers';25import {MultiLocation} from '@polkadot/types/interfaces';26import {blake2AsHex} from '@polkadot/util-crypto';27import waitNewBlocks from '../substrate/wait-new-blocks';28import getBalance from '../substrate/get-balance';29import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';3031chai.use(chaiAsPromised);32const expect = chai.expect;3334const UNIQUE_CHAIN = 2037;35const ACALA_CHAIN = 2000;36const MOONBEAM_CHAIN = 2004;3738const RELAY_PORT = 9844;39const ACALA_PORT = 9946;40const MOONBEAM_PORT = 9947;4142const ACALA_DECIMALS = 12;4344const TRANSFER_AMOUNT = 2000000000000000000000000n;4546function parachainApiOptions(port: number): ApiOptions {47 return {48 provider: new WsProvider('ws://127.0.0.1:' + port.toString()),49 };50}5152function acalaOptions(): ApiOptions {53 return parachainApiOptions(ACALA_PORT);54}5556function moonbeamOptions(): ApiOptions {57 return parachainApiOptions(MOONBEAM_PORT);58}5960function relayOptions(): ApiOptions {61 return parachainApiOptions(RELAY_PORT);62}6364describe_xcm('Integration test: Exchanging tokens with Acala', () => {65 let alice: IKeyringPair;66 let randomAccount: IKeyringPair;67 68 let balanceUniqueTokenInit: bigint;69 let balanceUniqueTokenMiddle: bigint;70 let balanceUniqueTokenFinal: bigint;71 let balanceAcalaTokenInit: bigint;72 let balanceAcalaTokenMiddle: bigint;73 let balanceAcalaTokenFinal: bigint;74 let balanceUniqueForeignTokenInit: bigint;75 let balanceUniqueForeignTokenMiddle: bigint;76 let balanceUniqueForeignTokenFinal: bigint;77 78 before(async () => {79 await usingApi(async (api, privateKeyWrapper) => {80 alice = privateKeyWrapper('//Alice');81 randomAccount = generateKeyringPair();82 });8384 // Acala side85 await usingApi(86 async (api) => {87 const destination = {88 V0: {89 X2: [90 'Parent',91 {92 Parachain: UNIQUE_CHAIN,93 },94 ],95 },96 };97 98 const metadata = {99 name: 'UNQ',100 symbol: 'UNQ',101 decimals: 18,102 minimalBalance: 1,103 };104 105 const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);106 const sudoTx = api.tx.sudo.sudo(tx as any);107 const events = await submitTransactionAsync(alice, sudoTx);108 const result = getGenericResult(events);109 expect(result.success).to.be.true;110 111 const tx1 = api.tx.balances.transfer(randomAccount.address, 10000000000000n);112 const events1 = await submitTransactionAsync(alice, tx1);113 const result1 = getGenericResult(events1);114 expect(result1.success).to.be.true;115 116 [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]);117 {118 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;119 balanceUniqueForeignTokenInit = BigInt(free);120 }121 },122 acalaOptions(),123 );124 125 // Unique side126 await usingApi(async (api) => {127 const tx0 = api.tx.balances.transfer(randomAccount.address, 10n * TRANSFER_AMOUNT);128 const events0 = await submitTransactionAsync(alice, tx0);129 const result0 = getGenericResult(events0);130 expect(result0.success).to.be.true;131 132 [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]);133 });134 });135 136 it('Should connect and send UNQ to Acala', async () => {137 138 // Unique side139 await usingApi(async (api) => {140 141 const destination = {142 V0: {143 X2: [144 'Parent',145 {146 Parachain: ACALA_CHAIN,147 },148 ],149 },150 };151 152 const beneficiary = {153 V0: {154 X1: {155 AccountId32: {156 network: 'Any',157 id: randomAccount.addressRaw,158 },159 },160 },161 };162 163 const assets = {164 V1: [165 {166 id: {167 Concrete: {168 parents: 0,169 interior: 'Here',170 },171 },172 fun: {173 Fungible: TRANSFER_AMOUNT,174 },175 },176 ],177 };178 179 const feeAssetItem = 0;180 181 const weightLimit = {182 Limited: 5000000000,183 };184 185 const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);186 const events = await submitTransactionAsync(randomAccount, tx);187 const result = getGenericResult(events);188 expect(result.success).to.be.true;189 190 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]);191 192 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;193 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));194 expect(unqFees > 0n).to.be.true;195 });196 197 // Acala side198 await usingApi(199 async (api) => {200 await waitNewBlocks(api, 3);201 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;202 balanceUniqueForeignTokenMiddle = BigInt(free);203 204 [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]);205206 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;207 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;208209 console.log(210 '[Unique -> Acala] transaction fees on Acala: %s ACA',211 bigIntToDecimals(acaFees, ACALA_DECIMALS),212 );213 console.log('[Unique -> Acala] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));214 expect(acaFees == 0n).to.be.true;215 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;216 },217 acalaOptions(),218 );219 });220 221 it('Should connect to Acala and send UNQ back', async () => {222 223 // Acala side224 await usingApi(225 async (api) => {226 const destination = {227 V1: {228 parents: 1,229 interior: {230 X2: [231 {Parachain: UNIQUE_CHAIN},232 {233 AccountId32: {234 network: 'Any',235 id: randomAccount.addressRaw,236 },237 },238 ],239 },240 },241 };242 243 const id = {244 ForeignAsset: 0,245 };246247 const destWeight = 50000000;248 249 const tx = api.tx.xTokens.transfer(id, TRANSFER_AMOUNT, destination, destWeight);250 const events = await submitTransactionAsync(randomAccount, tx);251 const result = getGenericResult(events);252 expect(result.success).to.be.true;253 254 [balanceAcalaTokenFinal] = await getBalance(api, [randomAccount.address]);255 {256 const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any;257 balanceUniqueForeignTokenFinal = BigInt(free);258 }259 260 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;261 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;262263 console.log(264 '[Acala -> Unique] transaction fees on Acala: %s ACA',265 bigIntToDecimals(acaFees, ACALA_DECIMALS),266 );267 console.log('[Acala -> Unique] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));268269 expect(acaFees > 0).to.be.true;270 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;271 },272 acalaOptions(),273 );274275 // Unique side276 await usingApi(async (api) => {277 await waitNewBlocks(api, 3);278 279 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]);280 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;281 expect(actuallyDelivered > 0).to.be.true;282283 console.log('[Acala -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));284 285 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;286 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));287 expect(unqFees == 0n).to.be.true;288 });289 });290});291292// These tests are relevant only when the foreign asset pallet is disabled293describe('Integration test: Unique rejects non-native tokens', () => {294 let alice: IKeyringPair;295296 before(async () => {297 await usingApi(async (api, privateKeyWrapper) => {298 alice = privateKeyWrapper('//Alice');299 });300 });301 302 it('Unique rejects tokens from the Relay', async () => {303 await usingApi(async (api) => {304 const destination = {305 V1: {306 parents: 0,307 interior: {X1: {308 Parachain: UNIQUE_CHAIN,309 },310 },311 }};312313 const beneficiary = {314 V1: {315 parents: 0,316 interior: {X1: {317 AccountId32: {318 network: 'Any',319 id: alice.addressRaw,320 },321 }},322 },323 };324325 const assets = {326 V1: [327 {328 id: {329 Concrete: {330 parents: 0,331 interior: 'Here',332 },333 },334 fun: {335 Fungible: 50_000_000_000_000_000n,336 },337 },338 ],339 };340341 const feeAssetItem = 0;342343 const weightLimit = {344 Limited: 5_000_000_000,345 };346347 const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);348 const events = await submitTransactionAsync(alice, tx);349 const result = getGenericResult(events);350 expect(result.success).to.be.true;351 }, relayOptions());352353 await usingApi(async api => {354 const maxWaitBlocks = 3;355 const dmpQueueExecutedDownward = await waitEvent(356 api,357 maxWaitBlocks,358 'dmpQueue',359 'ExecutedDownward',360 );361362 expect(363 dmpQueueExecutedDownward != null,364 '[Relay] dmpQueue.ExecutedDownward event is expected',365 ).to.be.true;366367 const event = dmpQueueExecutedDownward!.event;368 const outcome = event.data[1] as XcmV2TraitsOutcome;369370 expect(371 outcome.isIncomplete,372 '[Relay] The outcome of the XCM should be `Incomplete`',373 ).to.be.true;374375 const incomplete = outcome.asIncomplete;376 expect(377 incomplete[1].toString() == 'AssetNotFound',378 '[Relay] The XCM error should be `AssetNotFound`',379 ).to.be.true;380 });381 });382383 it('Unique rejects ACA tokens from Acala', async () => {384 await usingApi(async (api) => {385 const destination = {386 V1: {387 parents: 1,388 interior: {389 X2: [390 {Parachain: UNIQUE_CHAIN},391 {392 AccountId32: {393 network: 'Any',394 id: alice.addressRaw,395 },396 },397 ],398 },399 },400 };401402 const id = {403 Token: 'ACA',404 };405406 const destWeight = 50000000;407408 const tx = api.tx.xTokens.transfer(id, 100_000_000_000, destination, destWeight);409 const events = await submitTransactionAsync(alice, tx);410 const result = getGenericResult(events);411 expect(result.success).to.be.true;412 }, acalaOptions());413414 await usingApi(async api => {415 const maxWaitBlocks = 3;416 const xcmpQueueFailEvent = await waitEvent(api, maxWaitBlocks, 'xcmpQueue', 'Fail');417418 expect(419 xcmpQueueFailEvent != null,420 '[Acala] xcmpQueue.FailEvent event is expected',421 ).to.be.true;422423 const event = xcmpQueueFailEvent!.event;424 const outcome = event.data[1] as XcmV2TraitsError;425426 expect(427 outcome.isUntrustedReserveLocation,428 '[Acala] The XCM error should be `UntrustedReserveLocation`',429 ).to.be.true;430 });431 });432});433434describe_xcm('Integration test: Exchanging UNQ with Moonbeam', () => {435436 // Unique constants437 let uniqueAlice: IKeyringPair;438 let uniqueAssetLocation;439440 let randomAccountUnique: IKeyringPair;441 let randomAccountMoonbeam: IKeyringPair;442443 // Moonbeam constants444 let assetId: string;445446 const moonbeamKeyring = new Keyring({type: 'ethereum'});447 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';448 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';449 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';450451 const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum');452 const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');453 const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');454455 const councilVotingThreshold = 2;456 const technicalCommitteeThreshold = 2;457 const votingPeriod = 3;458 const delayPeriod = 0;459460 const uniqueAssetMetadata = {461 name: 'xcUnique',462 symbol: 'xcUNQ',463 decimals: 18,464 isFrozen: false,465 minimalBalance: 1,466 };467468 let balanceUniqueTokenInit: bigint;469 let balanceUniqueTokenMiddle: bigint;470 let balanceUniqueTokenFinal: bigint;471 let balanceForeignUnqTokenInit: bigint;472 let balanceForeignUnqTokenMiddle: bigint;473 let balanceForeignUnqTokenFinal: bigint;474 let balanceGlmrTokenInit: bigint;475 let balanceGlmrTokenMiddle: bigint;476 let balanceGlmrTokenFinal: bigint;477478 before(async () => {479 await usingApi(async (api, privateKeyWrapper) => {480 uniqueAlice = privateKeyWrapper('//Alice');481 randomAccountUnique = generateKeyringPair();482 randomAccountMoonbeam = generateKeyringPair('ethereum');483484 balanceForeignUnqTokenInit = 0n;485 });486487 await usingApi(488 async (api) => {489490 // >>> Sponsoring Dorothy >>>491 console.log('Sponsoring Dorothy.......');492 const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n);493 const events0 = await submitTransactionAsync(alithAccount, tx0);494 const result0 = getGenericResult(events0);495 expect(result0.success).to.be.true;496 console.log('Sponsoring Dorothy.......DONE');497 // <<< Sponsoring Dorothy <<<498499 const sourceLocation: MultiLocation = api.createType(500 'MultiLocation',501 {502 parents: 1,503 interior: {X1: {Parachain: UNIQUE_CHAIN}},504 },505 );506507 uniqueAssetLocation = {XCM: sourceLocation};508 const existentialDeposit = 1;509 const isSufficient = true;510 const unitsPerSecond = '1';511 const numAssetsWeightHint = 0;512513 const registerTx = api.tx.assetManager.registerForeignAsset(514 uniqueAssetLocation,515 uniqueAssetMetadata,516 existentialDeposit,517 isSufficient,518 );519 console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || '');520521 const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond(522 uniqueAssetLocation,523 unitsPerSecond,524 numAssetsWeightHint,525 );526 console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || '');527528 const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]);529 console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || '');530531 // >>> Note motion preimage >>>532 console.log('Note motion preimage.......');533 const encodedProposal = batchCall?.method.toHex() || '';534 const proposalHash = blake2AsHex(encodedProposal);535 console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal);536 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);537 console.log('Encoded length %d', encodedProposal.length);538539 const tx1 = api.tx.democracy.notePreimage(encodedProposal);540 const events1 = await submitTransactionAsync(baltatharAccount, tx1);541 const result1 = getGenericResult(events1);542 expect(result1.success).to.be.true;543 console.log('Note motion preimage.......DONE');544 // <<< Note motion preimage <<<545546 // >>> Propose external motion through council >>>547 console.log('Propose external motion through council.......');548 const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash);549 const tx2 = api.tx.councilCollective.propose(550 councilVotingThreshold,551 externalMotion,552 externalMotion.encodedLength,553 );554 const events2 = await submitTransactionAsync(baltatharAccount, tx2);555 const result2 = getGenericResult(events2);556 expect(result2.success).to.be.true;557558 const encodedMotion = externalMotion?.method.toHex() || '';559 const motionHash = blake2AsHex(encodedMotion);560 console.log('Motion hash is %s', motionHash);561562 const tx3 = api.tx.councilCollective.vote(motionHash, 0, true);563 {564 const events3 = await submitTransactionAsync(dorothyAccount, tx3);565 const result3 = getGenericResult(events3);566 expect(result3.success).to.be.true;567 }568 {569 const events3 = await submitTransactionAsync(baltatharAccount, tx3);570 const result3 = getGenericResult(events3);571 expect(result3.success).to.be.true;572 }573574 const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength);575 const events4 = await submitTransactionAsync(dorothyAccount, tx4);576 const result4 = getGenericResult(events4);577 expect(result4.success).to.be.true;578 console.log('Propose external motion through council.......DONE');579 // <<< Propose external motion through council <<<580581 // >>> Fast track proposal through technical committee >>>582 console.log('Fast track proposal through technical committee.......');583 const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);584 const tx5 = api.tx.techCommitteeCollective.propose(585 technicalCommitteeThreshold,586 fastTrack,587 fastTrack.encodedLength,588 );589 const events5 = await submitTransactionAsync(alithAccount, tx5);590 const result5 = getGenericResult(events5);591 expect(result5.success).to.be.true;592593 const encodedFastTrack = fastTrack?.method.toHex() || '';594 const fastTrackHash = blake2AsHex(encodedFastTrack);595 console.log('FastTrack hash is %s', fastTrackHash);596597 const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1;598 const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true);599 {600 const events6 = await submitTransactionAsync(baltatharAccount, tx6);601 const result6 = getGenericResult(events6);602 expect(result6.success).to.be.true;603 }604 {605 const events6 = await submitTransactionAsync(alithAccount, tx6);606 const result6 = getGenericResult(events6);607 expect(result6.success).to.be.true;608 }609610 const tx7 = api.tx.techCommitteeCollective611 .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength);612 const events7 = await submitTransactionAsync(baltatharAccount, tx7);613 const result7 = getGenericResult(events7);614 expect(result7.success).to.be.true;615 console.log('Fast track proposal through technical committee.......DONE');616 // <<< Fast track proposal through technical committee <<<617618 // >>> Referendum voting >>>619 console.log('Referendum voting.......');620 const tx8 = api.tx.democracy.vote(621 0,622 {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}},623 );624 const events8 = await submitTransactionAsync(dorothyAccount, tx8);625 const result8 = getGenericResult(events8);626 expect(result8.success).to.be.true;627 console.log('Referendum voting.......DONE');628 // <<< Referendum voting <<<629630 // >>> Acquire Unique AssetId Info on Moonbeam >>>631 console.log('Acquire Unique AssetId Info on Moonbeam.......');632633 // Wait for the democracy execute634 await waitNewBlocks(api, 5);635636 assetId = (await api.query.assetManager.assetTypeId({637 XCM: sourceLocation,638 })).toString();639640 console.log('UNQ asset ID is %s', assetId);641 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');642 // >>> Acquire Unique AssetId Info on Moonbeam >>>643644 // >>> Sponsoring random Account >>>645 console.log('Sponsoring random Account.......');646 const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);647 const events10 = await submitTransactionAsync(baltatharAccount, tx10);648 const result10 = getGenericResult(events10);649 expect(result10.success).to.be.true;650 console.log('Sponsoring random Account.......DONE');651 // <<< Sponsoring random Account <<<652653 [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]);654 },655 moonbeamOptions(),656 );657658 await usingApi(async (api) => {659 const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT);660 const events0 = await submitTransactionAsync(uniqueAlice, tx0);661 const result0 = getGenericResult(events0);662 expect(result0.success).to.be.true;663664 [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]);665 });666 });667668 it('Should connect and send UNQ to Moonbeam', async () => {669 await usingApi(async (api) => {670 const currencyId = {671 NativeAssetId: 'Here',672 };673 const dest = {674 V1: {675 parents: 1,676 interior: {677 X2: [678 {Parachain: MOONBEAM_CHAIN},679 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},680 ],681 },682 },683 };684 const amount = TRANSFER_AMOUNT;685 const destWeight = 850000000;686687 const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight);688 const events = await submitTransactionAsync(randomAccountUnique, tx);689 const result = getGenericResult(events);690 expect(result.success).to.be.true;691692 [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]);693 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;694695 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;696 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', bigIntToDecimals(transactionFees));697 expect(transactionFees > 0).to.be.true;698 });699700 await usingApi(701 async (api) => {702 await waitNewBlocks(api, 3);703704 [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]);705706 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;707 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));708 expect(glmrFees == 0n).to.be.true;709710 const unqRandomAccountAsset = (711 await api.query.assets.account(assetId, randomAccountMoonbeam.address)712 ).toJSON()! as any;713714 balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']);715 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;716 console.log('[Unique -> Moonbeam] income %s UNQ', bigIntToDecimals(unqIncomeTransfer));717 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;718 },719 moonbeamOptions(),720 );721 });722723 it('Should connect to Moonbeam and send UNQ back', async () => {724 await usingApi(725 async (api) => {726 const asset = {727 V1: {728 id: {729 Concrete: {730 parents: 1,731 interior: {732 X1: {Parachain: UNIQUE_CHAIN},733 },734 },735 },736 fun: {737 Fungible: TRANSFER_AMOUNT,738 },739 },740 };741 const destination = {742 V1: {743 parents: 1,744 interior: {745 X2: [746 {Parachain: UNIQUE_CHAIN},747 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},748 ],749 },750 },751 };752 const destWeight = 50000000;753754 const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight);755 const events = await submitTransactionAsync(randomAccountMoonbeam, tx);756 const result = getGenericResult(events);757 expect(result.success).to.be.true;758759 [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]);760761 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;762 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', bigIntToDecimals(glmrFees));763 expect(glmrFees > 0).to.be.true;764765 const unqRandomAccountAsset = (766 await api.query.assets.account(assetId, randomAccountMoonbeam.address)767 ).toJSON()! as any;768769 expect(unqRandomAccountAsset).to.be.null;770771 balanceForeignUnqTokenFinal = 0n;772773 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;774 console.log('[Unique -> Moonbeam] outcome %s UNQ', bigIntToDecimals(unqOutcomeTransfer));775 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;776 },777 moonbeamOptions(),778 );779780 await usingApi(async (api) => {781 await waitNewBlocks(api, 3);782783 [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]);784 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;785 expect(actuallyDelivered > 0).to.be.true;786787 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', bigIntToDecimals(actuallyDelivered));788789 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;790 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', bigIntToDecimals(unqFees));791 expect(unqFees == 0n).to.be.true;792 });793 });794});