difftreelog
Merge pull request #923 from UniqueNetwork/fix/xcm-tests-wait-for-events
in: master
4 files changed
tests/src/scheduler.seqtest.tsdiffbeforeafterboth--- a/tests/src/scheduler.seqtest.ts
+++ b/tests/src/scheduler.seqtest.ts
@@ -16,7 +16,7 @@
import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';
import {IKeyringPair} from '@polkadot/types/types';
-import {DevUniqueHelper} from './util/playgrounds/unique.dev';
+import {DevUniqueHelper, Event} from './util/playgrounds/unique.dev';
describe('Scheduling token and balance transfers', () => {
let superuser: IKeyringPair;
@@ -411,19 +411,13 @@
const priority = 112;
await helper.getSudo().scheduler.changePriority(superuser, scheduledId, priority);
- const priorityChanged = await helper.wait.event(
- waitForBlocks,
- 'scheduler',
- 'PriorityChanged',
- );
-
- expect(priorityChanged !== null).to.be.true;
+ const priorityChanged = await helper.wait.expectEvent(waitForBlocks, Event.Scheduler.PriorityChanged);
- const [blockNumber, index] = priorityChanged!.event.data[0].toJSON() as any[];
+ const [blockNumber, index] = priorityChanged.task();
expect(blockNumber).to.be.equal(executionBlock);
expect(index).to.be.equal(0);
- expect(priorityChanged!.event.data[1].toString()).to.be.equal(priority.toString());
+ expect(priorityChanged.priority().toString()).to.be.equal(priority.toString());
});
itSub('Prioritized operations execute in valid order', async ({helper}) => {
@@ -668,13 +662,7 @@
await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
.to.be.rejectedWith(/BadOrigin/);
- const priorityChanged = await helper.wait.event(
- waitForBlocks,
- 'scheduler',
- 'PriorityChanged',
- );
-
- expect(priorityChanged === null).to.be.true;
+ await helper.wait.expectEvent(waitForBlocks, Event.Scheduler.PriorityChanged);
});
});
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -9,7 +9,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {EventRecord} from '@polkadot/types/interfaces';
import {ICrossAccountId, IPovInfo, TSigner} from './types';
-import {FrameSystemEventRecord} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';
import {VoidFn} from '@polkadot/api/types';
import {Pallets} from '..';
import {spawnSync} from 'child_process';
@@ -59,6 +59,130 @@
}
}
+export interface IEventHelper {
+ section(): string;
+
+ method(): string;
+
+ bindEventRecord(e: FrameSystemEventRecord): void;
+
+ raw(): FrameSystemEventRecord;
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventHelper(section: string, method: string) {
+ return class implements IEventHelper {
+ eventRecord: FrameSystemEventRecord | null;
+ _section: string;
+ _method: string;
+
+ constructor() {
+ this.eventRecord = null;
+ this._section = section;
+ this._method = method;
+ }
+
+ section(): string {
+ return this._section;
+ }
+
+ method(): string {
+ return this._method;
+ }
+
+ bindEventRecord(e: FrameSystemEventRecord) {
+ this.eventRecord = e;
+ }
+
+ raw() {
+ return this.eventRecord!;
+ }
+
+ eventJsonData<T = any>(index: number) {
+ return this.raw().event.data[index].toJSON() as T;
+ }
+
+ eventData<T>(index: number) {
+ return this.raw().event.data[index] as T;
+ }
+ };
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventSection(section: string) {
+ return class Section {
+ static section = section;
+
+ static Method(name: string) {
+ return EventHelper(Section.section, name);
+ }
+ };
+}
+
+export class Event {
+ static Democracy = class extends EventSection('democracy') {
+ static Started = class extends this.Method('Started') {
+ referendumIndex() {
+ return this.eventJsonData<number>(0);
+ }
+
+ threshold() {
+ return this.eventJsonData(1);
+ }
+ };
+
+ static Voted = class extends this.Method('Voted') {
+ voter() {
+ return this.eventJsonData(0);
+ }
+
+ referendumIndex() {
+ return this.eventJsonData<number>(1);
+ }
+
+ vote() {
+ return this.eventJsonData(2);
+ }
+ };
+
+ static Passed = class extends this.Method('Passed') {
+ referendumIndex() {
+ return this.eventJsonData<number>(0);
+ }
+ };
+ };
+
+ static Scheduler = class extends EventSection('scheduler') {
+ static PriorityChanged = class extends this.Method('PriorityChanged') {
+ task() {
+ return this.eventJsonData(0);
+ }
+
+ priority() {
+ return this.eventJsonData(1);
+ }
+ };
+ };
+
+ static XcmpQueue = class extends EventSection('xcmpQueue') {
+ static XcmpMessageSent = class extends this.Method('XcmpMessageSent') {
+ messageHash() {
+ return this.eventJsonData(0);
+ }
+ };
+
+ static Fail = class extends this.Method('Fail') {
+ messageHash() {
+ return this.eventJsonData(0);
+ }
+
+ outcome() {
+ return this.eventData<XcmV2TraitsError>(1);
+ }
+ };
+ };
+}
+
export class DevUniqueHelper extends UniqueHelper {
/**
* Arrange methods for tests
@@ -634,12 +758,12 @@
console.log('\t* Fast track proposal through technical committee.......DONE');
// <<< Fast track proposal through technical committee <<<
- const refIndexField = 0;
- const referendumIndex = await this.helper.wait.eventData<number>(3, 'democracy', 'Started', refIndexField);
+ const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+ const referendumIndex = democracyStarted.referendumIndex();
// >>> Referendum voting >>>
console.log(`\t* Referendum #${referendumIndex} voting.......`);
- await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex!, {
+ await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {
balance: 10_000_000_000_000_000_000n,
vote: {aye: true, conviction: 1},
});
@@ -647,7 +771,10 @@
// <<< Referendum voting <<<
// Wait the proposal to pass
- await this.helper.wait.event(3, 'democracy', 'Passed');
+ await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => {
+ return event.referendumIndex() == referendumIndex;
+ });
+
await this.helper.wait.newBlocks(1);
console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
@@ -794,13 +921,18 @@
return promise;
}
- event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
+ event<T extends IEventHelper>(
+ maxBlocksToWait: number,
+ eventHelperType: new () => T,
+ filter: (_: T) => boolean = () => { return true; },
+ ) {
// eslint-disable-next-line no-async-promise-executor
- const promise = new Promise<EventRecord | null>(async (resolve) => {
+ const promise = new Promise<T | null>(async (resolve) => {
const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
+ const eventHelper = new eventHelperType();
const blockNumber = header.number.toHuman();
const blockHash = header.hash;
- const eventIdStr = `${eventSection}.${eventMethod}`;
+ const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;
const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
@@ -809,16 +941,24 @@
const eventRecords = (await apiAt.query.system.events()) as any;
const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {
- return r.event.section == eventSection && r.event.method == eventMethod;
+ if (
+ r.event.section == eventHelper.section()
+ && r.event.method == eventHelper.method()
+ ) {
+ eventHelper.bindEventRecord(r);
+ return filter(eventHelper);
+ } else {
+ return false;
+ }
});
if (neededEvent) {
unsubscribe();
- resolve(neededEvent);
+ resolve(eventHelper);
} else if (maxBlocksToWait > 0) {
maxBlocksToWait--;
} else {
- this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);
+ this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);
unsubscribe();
resolve(null);
}
@@ -827,17 +967,18 @@
return promise;
}
- async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {
- const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);
-
- if (eventRecord == null) {
- return null;
+ async expectEvent<T extends IEventHelper>(
+ maxBlocksToWait: number,
+ eventHelperType: new () => T,
+ filter: (e: T) => boolean = () => { return true; },
+ ) {
+ const e = await this.event(maxBlocksToWait, eventHelperType, filter);
+ if (e == null) {
+ const eventHelper = new eventHelperType();
+ throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);
+ } else {
+ return e;
}
-
- const event = eventRecord!.event;
- const data = event.data[fieldIndex] as EventT;
-
- return data;
}
}
tests/src/xcm/xcmQuartz.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 {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {XcmV2TraitsError} from '../interfaces';20import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';21import {DevUniqueHelper} from '../util/playgrounds/unique.dev';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;27const SHIDEN_CHAIN = 2007;2829const STATEMINE_PALLET_INSTANCE = 50;3031const relayUrl = config.relayUrl;32const statemineUrl = config.statemineUrl;33const karuraUrl = config.karuraUrl;34const moonriverUrl = config.moonriverUrl;35const shidenUrl = config.shidenUrl;3637const RELAY_DECIMALS = 12;38const STATEMINE_DECIMALS = 12;39const KARURA_DECIMALS = 12;40const SHIDEN_DECIMALS = 18n;41const QTZ_DECIMALS = 18n;4243const TRANSFER_AMOUNT = 2000000000000000000000000n;4445const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4647const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4849const USDT_ASSET_ID = 100;50const USDT_ASSET_METADATA_DECIMALS = 18;51const USDT_ASSET_METADATA_NAME = 'USDT';52const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';53const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;54const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5556const SAFE_XCM_VERSION = 2;5758describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {59 let alice: IKeyringPair;60 let bob: IKeyringPair;6162 let balanceStmnBefore: bigint;63 let balanceStmnAfter: bigint;6465 let balanceQuartzBefore: bigint;66 let balanceQuartzAfter: bigint;67 let balanceQuartzFinal: bigint;6869 let balanceBobBefore: bigint;70 let balanceBobAfter: bigint;71 let balanceBobFinal: bigint;7273 let balanceBobRelayTokenBefore: bigint;74 let balanceBobRelayTokenAfter: bigint;757677 before(async () => {78 await usingPlaygrounds(async (helper, privateKey) => {79 alice = await privateKey('//Alice');80 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8182 // Set the default version to wrap the first message to other chains.83 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);84 });8586 await usingRelayPlaygrounds(relayUrl, async (helper) => {87 // Fund accounts on Statemine(t)88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);89 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);90 });9192 await usingStateminePlaygrounds(statemineUrl, async (helper) => {93 const sovereignFundingAmount = 3_500_000_000n;9495 await helper.assets.create(96 alice,97 USDT_ASSET_ID,98 alice.address,99 USDT_ASSET_METADATA_MINIMAL_BALANCE,100 );101 await helper.assets.setMetadata(102 alice,103 USDT_ASSET_ID,104 USDT_ASSET_METADATA_NAME,105 USDT_ASSET_METADATA_DESCRIPTION,106 USDT_ASSET_METADATA_DECIMALS,107 );108 await helper.assets.mint(109 alice,110 USDT_ASSET_ID,111 alice.address,112 USDT_ASSET_AMOUNT,113 );114115 // funding parachain sovereing account on Statemine(t).116 // The sovereign account should be created before any action117 // (the assets pallet on Statemine(t) check if the sovereign account exists)118 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);119 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);120 });121122123 await usingPlaygrounds(async (helper) => {124 const location = {125 V2: {126 parents: 1,127 interior: {X3: [128 {129 Parachain: STATEMINE_CHAIN,130 },131 {132 PalletInstance: STATEMINE_PALLET_INSTANCE,133 },134 {135 GeneralIndex: USDT_ASSET_ID,136 },137 ]},138 },139 };140141 const metadata =142 {143 name: USDT_ASSET_ID,144 symbol: USDT_ASSET_METADATA_NAME,145 decimals: USDT_ASSET_METADATA_DECIMALS,146 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,147 };148 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);149 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);150 });151152153 // Providing the relay currency to the quartz sender account154 // (fee for USDT XCM are paid in relay tokens)155 await usingRelayPlaygrounds(relayUrl, async (helper) => {156 const destination = {157 V2: {158 parents: 0,159 interior: {X1: {160 Parachain: QUARTZ_CHAIN,161 },162 },163 }};164165 const beneficiary = {166 V2: {167 parents: 0,168 interior: {X1: {169 AccountId32: {170 network: 'Any',171 id: alice.addressRaw,172 },173 }},174 },175 };176177 const assets = {178 V2: [179 {180 id: {181 Concrete: {182 parents: 0,183 interior: 'Here',184 },185 },186 fun: {187 Fungible: TRANSFER_AMOUNT_RELAY,188 },189 },190 ],191 };192193 const feeAssetItem = 0;194195 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');196 });197198 });199200 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {201 await usingStateminePlaygrounds(statemineUrl, async (helper) => {202 const dest = {203 V2: {204 parents: 1,205 interior: {X1: {206 Parachain: QUARTZ_CHAIN,207 },208 },209 }};210211 const beneficiary = {212 V2: {213 parents: 0,214 interior: {X1: {215 AccountId32: {216 network: 'Any',217 id: alice.addressRaw,218 },219 }},220 },221 };222223 const assets = {224 V2: [225 {226 id: {227 Concrete: {228 parents: 0,229 interior: {230 X2: [231 {232 PalletInstance: STATEMINE_PALLET_INSTANCE,233 },234 {235 GeneralIndex: USDT_ASSET_ID,236 },237 ]},238 },239 },240 fun: {241 Fungible: TRANSFER_AMOUNT,242 },243 },244 ],245 };246247 const feeAssetItem = 0;248249 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);250 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');251252 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);253254 // common good parachain take commission in it native token255 console.log(256 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',257 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),258 );259 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;260261 });262263264 // ensure that asset has been delivered265 await helper.wait.newBlocks(3);266267 // expext collection id will be with id 1268 const free = await helper.ft.getBalance(1, {Substrate: alice.address});269270 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);271272 console.log(273 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',274 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),275 );276 console.log(277 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',278 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),279 );280 // commission has not paid in USDT token281 expect(free).to.be.equal(TRANSFER_AMOUNT);282 // ... and parachain native token283 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;284 });285286 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {287 const destination = {288 V2: {289 parents: 1,290 interior: {X2: [291 {292 Parachain: STATEMINE_CHAIN,293 },294 {295 AccountId32: {296 network: 'Any',297 id: alice.addressRaw,298 },299 },300 ]},301 },302 };303304 const relayFee = 400_000_000_000_000n;305 const currencies: [any, bigint][] = [306 [307 {308 ForeignAssetId: 0,309 },310 TRANSFER_AMOUNT,311 ],312 [313 {314 NativeAssetId: 'Parent',315 },316 relayFee,317 ],318 ];319320 const feeItem = 1;321322 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');323324 // the commission has been paid in parachain native token325 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);326 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));327 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;328329 await usingStateminePlaygrounds(statemineUrl, async (helper) => {330 await helper.wait.newBlocks(3);331332 // The USDT token never paid fees. Its amount not changed from begin value.333 // Also check that xcm transfer has been succeeded334 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;335 });336 });337338 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {339 balanceBobBefore = await helper.balance.getSubstrate(bob.address);340 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});341342 await usingRelayPlaygrounds(relayUrl, async (helper) => {343 const destination = {344 V2: {345 parents: 0,346 interior: {X1: {347 Parachain: QUARTZ_CHAIN,348 },349 },350 }};351352 const beneficiary = {353 V2: {354 parents: 0,355 interior: {X1: {356 AccountId32: {357 network: 'Any',358 id: bob.addressRaw,359 },360 }},361 },362 };363364 const assets = {365 V2: [366 {367 id: {368 Concrete: {369 parents: 0,370 interior: 'Here',371 },372 },373 fun: {374 Fungible: TRANSFER_AMOUNT_RELAY,375 },376 },377 ],378 };379380 const feeAssetItem = 0;381382 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');383 });384385 await helper.wait.newBlocks(3);386387 balanceBobAfter = await helper.balance.getSubstrate(bob.address);388 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});389390 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;391 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;392 console.log(393 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',394 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),395 );396 console.log(397 '[Relay (Westend) -> Quartz] transaction fees: %s WND',398 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),399 );400 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);401 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;402 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;403 });404405 itSub('Should connect and send Relay token back', async ({helper}) => {406 let relayTokenBalanceBefore: bigint;407 let relayTokenBalanceAfter: bigint;408 await usingRelayPlaygrounds(relayUrl, async (helper) => {409 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);410 });411412 const destination = {413 V2: {414 parents: 1,415 interior: {416 X1:{417 AccountId32: {418 network: 'Any',419 id: bob.addressRaw,420 },421 },422 },423 },424 };425426 const currencies: any = [427 [428 {429 NativeAssetId: 'Parent',430 },431 TRANSFER_AMOUNT_RELAY,432 ],433 ];434435 const feeItem = 0;436437 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');438439 balanceBobFinal = await helper.balance.getSubstrate(bob.address);440 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));441442 await usingRelayPlaygrounds(relayUrl, async (helper) => {443 await helper.wait.newBlocks(10);444 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);445446 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;447 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));448 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;449 });450 });451});452453describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {454 let alice: IKeyringPair;455 let randomAccount: IKeyringPair;456457 let balanceQuartzTokenInit: bigint;458 let balanceQuartzTokenMiddle: bigint;459 let balanceQuartzTokenFinal: bigint;460 let balanceKaruraTokenInit: bigint;461 let balanceKaruraTokenMiddle: bigint;462 let balanceKaruraTokenFinal: bigint;463 let balanceQuartzForeignTokenInit: bigint;464 let balanceQuartzForeignTokenMiddle: bigint;465 let balanceQuartzForeignTokenFinal: bigint;466467 // computed by a test transfer from prod Quartz to prod Karura.468 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9469 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)470 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;471472 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;473474 before(async () => {475 await usingPlaygrounds(async (helper, privateKey) => {476 alice = await privateKey('//Alice');477 [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479 // Set the default version to wrap the first message to other chains.480 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481 });482483 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {484 const destination = {485 V1: {486 parents: 1,487 interior: {488 X1: {489 Parachain: QUARTZ_CHAIN,490 },491 },492 },493 };494495 const metadata = {496 name: 'Quartz',497 symbol: 'QTZ',498 decimals: 18,499 minimalBalance: 1000000000000000000n,500 };501502 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);503 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);504 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);505 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});506 });507508 await usingPlaygrounds(async (helper) => {509 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);510 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);511 });512 });513514 itSub('Should connect and send QTZ to Karura', async ({helper}) => {515 const destination = {516 V2: {517 parents: 1,518 interior: {519 X1: {520 Parachain: KARURA_CHAIN,521 },522 },523 },524 };525526 const beneficiary = {527 V2: {528 parents: 0,529 interior: {530 X1: {531 AccountId32: {532 network: 'Any',533 id: randomAccount.addressRaw,534 },535 },536 },537 },538 };539540 const assets = {541 V2: [542 {543 id: {544 Concrete: {545 parents: 0,546 interior: 'Here',547 },548 },549 fun: {550 Fungible: TRANSFER_AMOUNT,551 },552 },553 ],554 };555556 const feeAssetItem = 0;557558 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');559 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);560561 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;562 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;563 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));564565 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {566 await helper.wait.newBlocks(3);567568 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});569 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);570571 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;572 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;573 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;574575 console.log(576 '[Quartz -> Karura] transaction fees on Karura: %s KAR',577 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),578 );579 console.log(580 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',581 helper.util.bigIntToDecimals(karUnqFees),582 );583 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));584 expect(karFees == 0n).to.be.true;585 expect(586 karUnqFees == expectedKaruraIncomeFee,587 'Karura took different income fee, check the Karura foreign asset config',588 ).to.be.true;589 });590 });591592 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {593 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {594 const destination = {595 V1: {596 parents: 1,597 interior: {598 X2: [599 {Parachain: QUARTZ_CHAIN},600 {601 AccountId32: {602 network: 'Any',603 id: randomAccount.addressRaw,604 },605 },606 ],607 },608 },609 };610611 const id = {612 ForeignAsset: 0,613 };614615 await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');616 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);617 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);618619 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;620 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;621622 console.log(623 '[Karura -> Quartz] transaction fees on Karura: %s KAR',624 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),625 );626 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));627628 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;629 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;630 });631632 await helper.wait.newBlocks(3);633634 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);635 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;636 expect(actuallyDelivered > 0).to.be.true;637638 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));639640 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;641 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));642 expect(qtzFees == 0n).to.be.true;643 });644645 itSub('Karura can send only up to its balance', async ({helper}) => {646 // set Karura's sovereign account's balance647 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);648 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);649 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);650651 const moreThanKaruraHas = karuraBalance * 2n;652653 let targetAccountBalance = 0n;654 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);655656 const quartzMultilocation = {657 V1: {658 parents: 1,659 interior: {660 X1: {Parachain: QUARTZ_CHAIN},661 },662 },663 };664665 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(666 targetAccount.addressRaw,667 {668 Concrete: {669 parents: 0,670 interior: 'Here',671 },672 },673 moreThanKaruraHas,674 );675676 // Try to trick Quartz677 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {678 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);679 });680681 const maxWaitBlocks = 3;682 const outcomeField = 1;683684 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(685 maxWaitBlocks,686 'xcmpQueue',687 'Fail',688 outcomeField,689 );690691 expect(692 xcmpQueueFailEvent != null,693 '\'xcmpQueue.FailEvent\' event is expected',694 ).to.be.true;695696 expect(697 xcmpQueueFailEvent!.isFailedToTransactAsset,698 `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,699 ).to.be.true;700701 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);702 expect(targetAccountBalance).to.be.equal(0n);703704 // But Karura still can send the correct amount705 const validTransferAmount = karuraBalance / 2n;706 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(707 targetAccount.addressRaw,708 {709 Concrete: {710 parents: 0,711 interior: 'Here',712 },713 },714 validTransferAmount,715 );716717 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {718 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);719 });720721 await helper.wait.newBlocks(maxWaitBlocks);722723 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);724 expect(targetAccountBalance).to.be.equal(validTransferAmount);725 });726727 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {728 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);729 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);730731 const quartzMultilocation = {732 V1: {733 parents: 1,734 interior: {735 X1: {736 Parachain: QUARTZ_CHAIN,737 },738 },739 },740 };741742 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(743 targetAccount.addressRaw,744 {745 Concrete: {746 parents: 1,747 interior: {748 X1: {749 Parachain: QUARTZ_CHAIN,750 },751 },752 },753 },754 testAmount,755 );756757 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(758 targetAccount.addressRaw,759 {760 Concrete: {761 parents: 0,762 interior: 'Here',763 },764 },765 testAmount,766 );767768 // Try to trick Quartz using full QTZ identification769 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {770 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);771 });772773 const maxWaitBlocks = 3;774 const outcomeField = 1;775776 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(777 maxWaitBlocks,778 'xcmpQueue',779 'Fail',780 outcomeField,781 );782783 expect(784 xcmpQueueFailEvent != null,785 '\'xcmpQueue.FailEvent\' event is expected',786 ).to.be.true;787788 expect(789 xcmpQueueFailEvent!.isUntrustedReserveLocation,790 `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,791 ).to.be.true;792793 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);794 expect(accountBalance).to.be.equal(0n);795796 // Try to trick Quartz using shortened QTZ identification797 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {798 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);799 });800801 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(802 maxWaitBlocks,803 'xcmpQueue',804 'Fail',805 outcomeField,806 );807808 expect(809 xcmpQueueFailEvent != null,810 '\'xcmpQueue.FailEvent\' event is expected',811 ).to.be.true;812813 expect(814 xcmpQueueFailEvent!.isUntrustedReserveLocation,815 `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,816 ).to.be.true;817818 accountBalance = await helper.balance.getSubstrate(targetAccount.address);819 expect(accountBalance).to.be.equal(0n);820 });821});822823// These tests are relevant only when824// the the corresponding foreign assets are not registered825describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {826 let alice: IKeyringPair;827 let alith: IKeyringPair;828829 const testAmount = 100_000_000_000n;830 let quartzParachainJunction;831 let quartzAccountJunction;832833 let quartzParachainMultilocation: any;834 let quartzAccountMultilocation: any;835 let quartzCombinedMultilocation: any;836837 before(async () => {838 await usingPlaygrounds(async (helper, privateKey) => {839 alice = await privateKey('//Alice');840841 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};842 quartzAccountJunction = {843 AccountId32: {844 network: 'Any',845 id: alice.addressRaw,846 },847 };848849 quartzParachainMultilocation = {850 V1: {851 parents: 1,852 interior: {853 X1: quartzParachainJunction,854 },855 },856 };857858 quartzAccountMultilocation = {859 V1: {860 parents: 0,861 interior: {862 X1: quartzAccountJunction,863 },864 },865 };866867 quartzCombinedMultilocation = {868 V1: {869 parents: 1,870 interior: {871 X2: [quartzParachainJunction, quartzAccountJunction],872 },873 },874 };875876 // Set the default version to wrap the first message to other chains.877 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);878 });879880 // eslint-disable-next-line require-await881 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {882 alith = helper.account.alithAccount();883 });884 });885886 const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {887 const maxWaitBlocks = 3;888 const outcomeField = 1;889890 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(891 maxWaitBlocks,892 'xcmpQueue',893 'Fail',894 outcomeField,895 );896897 expect(898 xcmpQueueFailEvent != null,899 `[reject ${network} tokens] 'xcmpQueue.FailEvent' event is expected`,900 ).to.be.true;901902 expect(903 xcmpQueueFailEvent!.isFailedToTransactAsset,904 `[reject ${network} tokens] The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,905 ).to.be.true;906 };907908 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {909 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {910 const id = {911 Token: 'KAR',912 };913 const destination = quartzCombinedMultilocation;914 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');915 });916917 await expectFailedToTransact('KAR', helper);918 });919920 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {921 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {922 const id = 'SelfReserve';923 const destination = quartzCombinedMultilocation;924 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');925 });926927 await expectFailedToTransact('MOVR', helper);928 });929930 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {931 await usingShidenPlaygrounds(shidenUrl, async (helper) => {932 const destinationParachain = quartzParachainMultilocation;933 const beneficiary = quartzAccountMultilocation;934 const assets = {935 V1: [{936 id: {937 Concrete: {938 parents: 0,939 interior: 'Here',940 },941 },942 fun: {943 Fungible: testAmount,944 },945 }],946 };947 const feeAssetItem = 0;948949 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [950 destinationParachain,951 beneficiary,952 assets,953 feeAssetItem,954 ]);955 });956957 await expectFailedToTransact('SDN', helper);958 });959});960961describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {962 // Quartz constants963 let alice: IKeyringPair;964 let quartzAssetLocation;965966 let randomAccountQuartz: IKeyringPair;967 let randomAccountMoonriver: IKeyringPair;968969 // Moonriver constants970 let assetId: string;971972 const quartzAssetMetadata = {973 name: 'xcQuartz',974 symbol: 'xcQTZ',975 decimals: 18,976 isFrozen: false,977 minimalBalance: 1n,978 };979980 let balanceQuartzTokenInit: bigint;981 let balanceQuartzTokenMiddle: bigint;982 let balanceQuartzTokenFinal: bigint;983 let balanceForeignQtzTokenInit: bigint;984 let balanceForeignQtzTokenMiddle: bigint;985 let balanceForeignQtzTokenFinal: bigint;986 let balanceMovrTokenInit: bigint;987 let balanceMovrTokenMiddle: bigint;988 let balanceMovrTokenFinal: bigint;989990 before(async () => {991 await usingPlaygrounds(async (helper, privateKey) => {992 alice = await privateKey('//Alice');993 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);994995 balanceForeignQtzTokenInit = 0n;996997 // Set the default version to wrap the first message to other chains.998 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);999 });10001001 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1002 const alithAccount = helper.account.alithAccount();1003 const baltatharAccount = helper.account.baltatharAccount();1004 const dorothyAccount = helper.account.dorothyAccount();10051006 randomAccountMoonriver = helper.account.create();10071008 // >>> Sponsoring Dorothy >>>1009 console.log('Sponsoring Dorothy.......');1010 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1011 console.log('Sponsoring Dorothy.......DONE');1012 // <<< Sponsoring Dorothy <<<10131014 quartzAssetLocation = {1015 XCM: {1016 parents: 1,1017 interior: {X1: {Parachain: QUARTZ_CHAIN}},1018 },1019 };1020 const existentialDeposit = 1n;1021 const isSufficient = true;1022 const unitsPerSecond = 1n;1023 const numAssetsWeightHint = 0;10241025 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1026 location: quartzAssetLocation,1027 metadata: quartzAssetMetadata,1028 existentialDeposit,1029 isSufficient,1030 unitsPerSecond,1031 numAssetsWeightHint,1032 });10331034 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10351036 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10371038 // >>> Acquire Quartz AssetId Info on Moonriver >>>1039 console.log('Acquire Quartz AssetId Info on Moonriver.......');10401041 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10421043 console.log('QTZ asset ID is %s', assetId);1044 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1045 // >>> Acquire Quartz AssetId Info on Moonriver >>>10461047 // >>> Sponsoring random Account >>>1048 console.log('Sponsoring random Account.......');1049 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1050 console.log('Sponsoring random Account.......DONE');1051 // <<< Sponsoring random Account <<<10521053 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1054 });10551056 await usingPlaygrounds(async (helper) => {1057 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1058 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1059 });1060 });10611062 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1063 const currencyId = {1064 NativeAssetId: 'Here',1065 };1066 const dest = {1067 V2: {1068 parents: 1,1069 interior: {1070 X2: [1071 {Parachain: MOONRIVER_CHAIN},1072 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1073 ],1074 },1075 },1076 };1077 const amount = TRANSFER_AMOUNT;10781079 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10801081 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1082 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10831084 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1085 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1086 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10871088 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1089 await helper.wait.newBlocks(3);10901091 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10921093 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1094 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1095 expect(movrFees == 0n).to.be.true;10961097 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1098 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1099 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1100 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1101 });1102 });11031104 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1105 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1106 const asset = {1107 V1: {1108 id: {1109 Concrete: {1110 parents: 1,1111 interior: {1112 X1: {Parachain: QUARTZ_CHAIN},1113 },1114 },1115 },1116 fun: {1117 Fungible: TRANSFER_AMOUNT,1118 },1119 },1120 };1121 const destination = {1122 V1: {1123 parents: 1,1124 interior: {1125 X2: [1126 {Parachain: QUARTZ_CHAIN},1127 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1128 ],1129 },1130 },1131 };11321133 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');11341135 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11361137 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1138 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1139 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11401141 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11421143 expect(qtzRandomAccountAsset).to.be.null;11441145 balanceForeignQtzTokenFinal = 0n;11461147 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1148 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1149 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1150 });11511152 await helper.wait.newBlocks(3);11531154 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1155 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1156 expect(actuallyDelivered > 0).to.be.true;11571158 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11591160 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1161 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1162 expect(qtzFees == 0n).to.be.true;1163 });11641165 itSub('Moonriver can send only up to its balance', async ({helper}) => {1166 // set Moonriver's sovereign account's balance1167 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1168 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1169 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11701171 const moreThanMoonriverHas = moonriverBalance * 2n;11721173 let targetAccountBalance = 0n;1174 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11751176 const quartzMultilocation = {1177 V1: {1178 parents: 1,1179 interior: {1180 X1: {Parachain: QUARTZ_CHAIN},1181 },1182 },1183 };11841185 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1186 targetAccount.addressRaw,1187 {1188 Concrete: {1189 parents: 0,1190 interior: 'Here',1191 },1192 },1193 moreThanMoonriverHas,1194 );11951196 // Try to trick Quartz1197 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1198 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11991200 // Needed to bypass the call filter.1201 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1202 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);1203 });12041205 const maxWaitBlocks = 3;1206 const outcomeField = 1;12071208 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1209 maxWaitBlocks,1210 'xcmpQueue',1211 'Fail',1212 outcomeField,1213 );12141215 expect(1216 xcmpQueueFailEvent != null,1217 '\'xcmpQueue.FailEvent\' event is expected',1218 ).to.be.true;12191220 expect(1221 xcmpQueueFailEvent!.isFailedToTransactAsset,1222 `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,1223 ).to.be.true;12241225 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1226 expect(targetAccountBalance).to.be.equal(0n);12271228 // But Moonriver still can send the correct amount1229 const validTransferAmount = moonriverBalance / 2n;1230 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1231 targetAccount.addressRaw,1232 {1233 Concrete: {1234 parents: 0,1235 interior: 'Here',1236 },1237 },1238 validTransferAmount,1239 );12401241 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1242 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);12431244 // Needed to bypass the call filter.1245 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1246 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1247 });12481249 await helper.wait.newBlocks(maxWaitBlocks);12501251 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1252 expect(targetAccountBalance).to.be.equal(validTransferAmount);1253 });12541255 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1256 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1257 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12581259 const quartzMultilocation = {1260 V1: {1261 parents: 1,1262 interior: {1263 X1: {1264 Parachain: QUARTZ_CHAIN,1265 },1266 },1267 },1268 };12691270 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1271 targetAccount.addressRaw,1272 {1273 Concrete: {1274 parents: 0,1275 interior: {1276 X1: {1277 Parachain: QUARTZ_CHAIN,1278 },1279 },1280 },1281 },1282 testAmount,1283 );12841285 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1286 targetAccount.addressRaw,1287 {1288 Concrete: {1289 parents: 0,1290 interior: 'Here',1291 },1292 },1293 testAmount,1294 );12951296 // Try to trick Quartz using full QTZ identification1297 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1298 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12991300 // Needed to bypass the call filter.1301 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1302 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);1303 });13041305 const maxWaitBlocks = 3;1306 const outcomeField = 1;13071308 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1309 maxWaitBlocks,1310 'xcmpQueue',1311 'Fail',1312 outcomeField,1313 );13141315 expect(1316 xcmpQueueFailEvent != null,1317 '\'xcmpQueue.FailEvent\' event is expected',1318 ).to.be.true;13191320 expect(1321 xcmpQueueFailEvent!.isUntrustedReserveLocation,1322 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1323 ).to.be.true;13241325 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1326 expect(accountBalance).to.be.equal(0n);13271328 // Try to trick Quartz using shortened QTZ identification1329 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1330 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);13311332 // Needed to bypass the call filter.1333 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1334 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);1335 });13361337 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1338 maxWaitBlocks,1339 'xcmpQueue',1340 'Fail',1341 outcomeField,1342 );13431344 expect(1345 xcmpQueueFailEvent != null,1346 '\'xcmpQueue.FailEvent\' event is expected',1347 ).to.be.true;13481349 expect(1350 xcmpQueueFailEvent!.isUntrustedReserveLocation,1351 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1352 ).to.be.true;13531354 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1355 expect(accountBalance).to.be.equal(0n);1356 });1357});13581359describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1360 let alice: IKeyringPair;1361 let sender: IKeyringPair;13621363 const QTZ_ASSET_ID_ON_SHIDEN = 1;1364 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;13651366 // Quartz -> Shiden1367 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1368 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1369 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1370 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens13711372 // Shiden -> Quartz1373 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1374 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ13751376 let balanceAfterQuartzToShidenXCM: bigint;13771378 before(async () => {1379 await usingPlaygrounds(async (helper, privateKey) => {1380 alice = await privateKey('//Alice');1381 [sender] = await helper.arrange.createAccounts([100n], alice);1382 console.log('sender', sender.address);13831384 // Set the default version to wrap the first message to other chains.1385 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1386 });13871388 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1389 console.log('1. Create foreign asset and metadata');1390 // TODO update metadata with values from production1391 await helper.assets.create(1392 alice,1393 QTZ_ASSET_ID_ON_SHIDEN,1394 alice.address,1395 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1396 );13971398 await helper.assets.setMetadata(1399 alice,1400 QTZ_ASSET_ID_ON_SHIDEN,1401 'Cross chain QTZ',1402 'xcQTZ',1403 Number(QTZ_DECIMALS),1404 );14051406 console.log('2. Register asset location on Shiden');1407 const assetLocation = {1408 V1: {1409 parents: 1,1410 interior: {1411 X1: {1412 Parachain: QUARTZ_CHAIN,1413 },1414 },1415 },1416 };14171418 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);14191420 console.log('3. Set QTZ payment for XCM execution on Shiden');1421 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);14221423 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1424 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1425 });1426 });14271428 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1429 const destination = {1430 V2: {1431 parents: 1,1432 interior: {1433 X1: {1434 Parachain: SHIDEN_CHAIN,1435 },1436 },1437 },1438 };14391440 const beneficiary = {1441 V2: {1442 parents: 0,1443 interior: {1444 X1: {1445 AccountId32: {1446 network: 'Any',1447 id: sender.addressRaw,1448 },1449 },1450 },1451 },1452 };14531454 const assets = {1455 V2: [1456 {1457 id: {1458 Concrete: {1459 parents: 0,1460 interior: 'Here',1461 },1462 },1463 fun: {1464 Fungible: qtzToShidenTransferred,1465 },1466 },1467 ],1468 };14691470 // Initial balance is 100 QTZ1471 const balanceBefore = await helper.balance.getSubstrate(sender.address);1472 console.log(`Initial balance is: ${balanceBefore}`);14731474 const feeAssetItem = 0;1475 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14761477 // Balance after reserve transfer is less than 901478 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1479 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1480 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1481 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14821483 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1484 await helper.wait.newBlocks(3);1485 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1486 const shidenBalance = await helper.balance.getSubstrate(sender.address);14871488 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1489 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14901491 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1492 // SHD balance does not changed:1493 expect(shidenBalance).to.eq(shidenInitialBalance);1494 });1495 });14961497 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1498 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1499 const destination = {1500 V1: {1501 parents: 1,1502 interior: {1503 X1: {1504 Parachain: QUARTZ_CHAIN,1505 },1506 },1507 },1508 };15091510 const beneficiary = {1511 V1: {1512 parents: 0,1513 interior: {1514 X1: {1515 AccountId32: {1516 network: 'Any',1517 id: sender.addressRaw,1518 },1519 },1520 },1521 },1522 };15231524 const assets = {1525 V1: [1526 {1527 id: {1528 Concrete: {1529 parents: 1,1530 interior: {1531 X1: {1532 Parachain: QUARTZ_CHAIN,1533 },1534 },1535 },1536 },1537 fun: {1538 Fungible: qtzFromShidenTransfered,1539 },1540 },1541 ],1542 };15431544 // Initial balance is 1 SDN1545 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1546 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1547 expect(balanceSDNbefore).to.eq(shidenInitialBalance);15481549 const feeAssetItem = 0;1550 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1551 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);15521553 // Balance after reserve transfer is less than 1 SDN1554 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1555 const balanceSDN = await helper.balance.getSubstrate(sender.address);1556 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);15571558 // Assert: xcQTZ balance correctly decreased1559 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1560 // Assert: SDN balance is 0.996...1561 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1562 });15631564 await helper.wait.newBlocks(3);1565 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1566 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1567 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1568 });15691570 itSub('Shiden can send only up to its balance', async ({helper}) => {1571 // set Shiden's sovereign account's balance1572 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1573 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1574 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15751576 const moreThanShidenHas = shidenBalance * 2n;15771578 let targetAccountBalance = 0n;1579 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15801581 const quartzMultilocation = {1582 V1: {1583 parents: 1,1584 interior: {1585 X1: {Parachain: QUARTZ_CHAIN},1586 },1587 },1588 };15891590 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1591 targetAccount.addressRaw,1592 {1593 Concrete: {1594 parents: 0,1595 interior: 'Here',1596 },1597 },1598 moreThanShidenHas,1599 );16001601 // Try to trick Quartz1602 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1603 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);1604 });16051606 const maxWaitBlocks = 3;1607 const outcomeField = 1;16081609 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1610 maxWaitBlocks,1611 'xcmpQueue',1612 'Fail',1613 outcomeField,1614 );16151616 expect(1617 xcmpQueueFailEvent != null,1618 '\'xcmpQueue.FailEvent\' event is expected',1619 ).to.be.true;16201621 expect(1622 xcmpQueueFailEvent!.isFailedToTransactAsset,1623 `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,1624 ).to.be.true;16251626 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1627 expect(targetAccountBalance).to.be.equal(0n);16281629 // But Shiden still can send the correct amount1630 const validTransferAmount = shidenBalance / 2n;1631 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1632 targetAccount.addressRaw,1633 {1634 Concrete: {1635 parents: 0,1636 interior: 'Here',1637 },1638 },1639 validTransferAmount,1640 );16411642 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1643 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1644 });16451646 await helper.wait.newBlocks(maxWaitBlocks);16471648 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1649 expect(targetAccountBalance).to.be.equal(validTransferAmount);1650 });16511652 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1653 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1654 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);16551656 const quartzMultilocation = {1657 V1: {1658 parents: 1,1659 interior: {1660 X1: {1661 Parachain: QUARTZ_CHAIN,1662 },1663 },1664 },1665 };16661667 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1668 targetAccount.addressRaw,1669 {1670 Concrete: {1671 parents: 1,1672 interior: {1673 X1: {1674 Parachain: QUARTZ_CHAIN,1675 },1676 },1677 },1678 },1679 testAmount,1680 );16811682 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1683 targetAccount.addressRaw,1684 {1685 Concrete: {1686 parents: 0,1687 interior: 'Here',1688 },1689 },1690 testAmount,1691 );16921693 // Try to trick Quartz using full QTZ identification1694 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1695 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);1696 });16971698 const maxWaitBlocks = 3;1699 const outcomeField = 1;17001701 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1702 maxWaitBlocks,1703 'xcmpQueue',1704 'Fail',1705 outcomeField,1706 );17071708 expect(1709 xcmpQueueFailEvent != null,1710 '\'xcmpQueue.FailEvent\' event is expected',1711 ).to.be.true;17121713 expect(1714 xcmpQueueFailEvent!.isUntrustedReserveLocation,1715 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1716 ).to.be.true;17171718 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1719 expect(accountBalance).to.be.equal(0n);17201721 // Try to trick Quartz using shortened QTZ identification1722 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1723 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);1724 });17251726 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(1727 maxWaitBlocks,1728 'xcmpQueue',1729 'Fail',1730 outcomeField,1731 );17321733 expect(1734 xcmpQueueFailEvent != null,1735 '\'xcmpQueue.FailEvent\' event is expected',1736 ).to.be.true;17371738 expect(1739 xcmpQueueFailEvent!.isUntrustedReserveLocation,1740 `The XCM error should be 'isUntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,1741 ).to.be.true;17421743 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1744 expect(accountBalance).to.be.equal(0n);1745 });1746});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 {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const QUARTZ_CHAIN = 2095;23const STATEMINE_CHAIN = 1000;24const KARURA_CHAIN = 2000;25const MOONRIVER_CHAIN = 2023;26const SHIDEN_CHAIN = 2007;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;34const shidenUrl = config.shidenUrl;3536const RELAY_DECIMALS = 12;37const STATEMINE_DECIMALS = 12;38const KARURA_DECIMALS = 12;39const SHIDEN_DECIMALS = 18n;40const QTZ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {58 let alice: IKeyringPair;59 let bob: IKeyringPair;6061 let balanceStmnBefore: bigint;62 let balanceStmnAfter: bigint;6364 let balanceQuartzBefore: bigint;65 let balanceQuartzAfter: bigint;66 let balanceQuartzFinal: bigint;6768 let balanceBobBefore: bigint;69 let balanceBobAfter: bigint;70 let balanceBobFinal: bigint;7172 let balanceBobRelayTokenBefore: bigint;73 let balanceBobRelayTokenAfter: bigint;747576 before(async () => {77 await usingPlaygrounds(async (helper, privateKey) => {78 alice = await privateKey('//Alice');79 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor8081 // Set the default version to wrap the first message to other chains.82 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83 });8485 await usingRelayPlaygrounds(relayUrl, async (helper) => {86 // Fund accounts on Statemine(t)87 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);88 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);89 });9091 await usingStateminePlaygrounds(statemineUrl, async (helper) => {92 const sovereignFundingAmount = 3_500_000_000n;9394 await helper.assets.create(95 alice,96 USDT_ASSET_ID,97 alice.address,98 USDT_ASSET_METADATA_MINIMAL_BALANCE,99 );100 await helper.assets.setMetadata(101 alice,102 USDT_ASSET_ID,103 USDT_ASSET_METADATA_NAME,104 USDT_ASSET_METADATA_DESCRIPTION,105 USDT_ASSET_METADATA_DECIMALS,106 );107 await helper.assets.mint(108 alice,109 USDT_ASSET_ID,110 alice.address,111 USDT_ASSET_AMOUNT,112 );113114 // funding parachain sovereing account on Statemine(t).115 // The sovereign account should be created before any action116 // (the assets pallet on Statemine(t) check if the sovereign account exists)117 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);118 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);119 });120121122 await usingPlaygrounds(async (helper) => {123 const location = {124 V2: {125 parents: 1,126 interior: {X3: [127 {128 Parachain: STATEMINE_CHAIN,129 },130 {131 PalletInstance: STATEMINE_PALLET_INSTANCE,132 },133 {134 GeneralIndex: USDT_ASSET_ID,135 },136 ]},137 },138 };139140 const metadata =141 {142 name: USDT_ASSET_ID,143 symbol: USDT_ASSET_METADATA_NAME,144 decimals: USDT_ASSET_METADATA_DECIMALS,145 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,146 };147 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);148 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);149 });150151152 // Providing the relay currency to the quartz sender account153 // (fee for USDT XCM are paid in relay tokens)154 await usingRelayPlaygrounds(relayUrl, async (helper) => {155 const destination = {156 V2: {157 parents: 0,158 interior: {X1: {159 Parachain: QUARTZ_CHAIN,160 },161 },162 }};163164 const beneficiary = {165 V2: {166 parents: 0,167 interior: {X1: {168 AccountId32: {169 network: 'Any',170 id: alice.addressRaw,171 },172 }},173 },174 };175176 const assets = {177 V2: [178 {179 id: {180 Concrete: {181 parents: 0,182 interior: 'Here',183 },184 },185 fun: {186 Fungible: TRANSFER_AMOUNT_RELAY,187 },188 },189 ],190 };191192 const feeAssetItem = 0;193194 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');195 });196197 });198199 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {200 await usingStateminePlaygrounds(statemineUrl, async (helper) => {201 const dest = {202 V2: {203 parents: 1,204 interior: {X1: {205 Parachain: QUARTZ_CHAIN,206 },207 },208 }};209210 const beneficiary = {211 V2: {212 parents: 0,213 interior: {X1: {214 AccountId32: {215 network: 'Any',216 id: alice.addressRaw,217 },218 }},219 },220 };221222 const assets = {223 V2: [224 {225 id: {226 Concrete: {227 parents: 0,228 interior: {229 X2: [230 {231 PalletInstance: STATEMINE_PALLET_INSTANCE,232 },233 {234 GeneralIndex: USDT_ASSET_ID,235 },236 ]},237 },238 },239 fun: {240 Fungible: TRANSFER_AMOUNT,241 },242 },243 ],244 };245246 const feeAssetItem = 0;247248 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);249 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');250251 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);252253 // common good parachain take commission in it native token254 console.log(255 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',256 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),257 );258 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;259260 });261262263 // ensure that asset has been delivered264 await helper.wait.newBlocks(3);265266 // expext collection id will be with id 1267 const free = await helper.ft.getBalance(1, {Substrate: alice.address});268269 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);270271 console.log(272 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',273 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),274 );275 console.log(276 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',277 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),278 );279 // commission has not paid in USDT token280 expect(free).to.be.equal(TRANSFER_AMOUNT);281 // ... and parachain native token282 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;283 });284285 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {286 const destination = {287 V2: {288 parents: 1,289 interior: {X2: [290 {291 Parachain: STATEMINE_CHAIN,292 },293 {294 AccountId32: {295 network: 'Any',296 id: alice.addressRaw,297 },298 },299 ]},300 },301 };302303 const relayFee = 400_000_000_000_000n;304 const currencies: [any, bigint][] = [305 [306 {307 ForeignAssetId: 0,308 },309 TRANSFER_AMOUNT,310 ],311 [312 {313 NativeAssetId: 'Parent',314 },315 relayFee,316 ],317 ];318319 const feeItem = 1;320321 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');322323 // the commission has been paid in parachain native token324 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);325 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));326 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;327328 await usingStateminePlaygrounds(statemineUrl, async (helper) => {329 await helper.wait.newBlocks(3);330331 // The USDT token never paid fees. Its amount not changed from begin value.332 // Also check that xcm transfer has been succeeded333 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;334 });335 });336337 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {338 balanceBobBefore = await helper.balance.getSubstrate(bob.address);339 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});340341 await usingRelayPlaygrounds(relayUrl, async (helper) => {342 const destination = {343 V2: {344 parents: 0,345 interior: {X1: {346 Parachain: QUARTZ_CHAIN,347 },348 },349 }};350351 const beneficiary = {352 V2: {353 parents: 0,354 interior: {X1: {355 AccountId32: {356 network: 'Any',357 id: bob.addressRaw,358 },359 }},360 },361 };362363 const assets = {364 V2: [365 {366 id: {367 Concrete: {368 parents: 0,369 interior: 'Here',370 },371 },372 fun: {373 Fungible: TRANSFER_AMOUNT_RELAY,374 },375 },376 ],377 };378379 const feeAssetItem = 0;380381 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');382 });383384 await helper.wait.newBlocks(3);385386 balanceBobAfter = await helper.balance.getSubstrate(bob.address);387 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});388389 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;390 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;391 console.log(392 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',393 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),394 );395 console.log(396 '[Relay (Westend) -> Quartz] transaction fees: %s WND',397 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),398 );399 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);400 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;401 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;402 });403404 itSub('Should connect and send Relay token back', async ({helper}) => {405 let relayTokenBalanceBefore: bigint;406 let relayTokenBalanceAfter: bigint;407 await usingRelayPlaygrounds(relayUrl, async (helper) => {408 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);409 });410411 const destination = {412 V2: {413 parents: 1,414 interior: {415 X1:{416 AccountId32: {417 network: 'Any',418 id: bob.addressRaw,419 },420 },421 },422 },423 };424425 const currencies: any = [426 [427 {428 NativeAssetId: 'Parent',429 },430 TRANSFER_AMOUNT_RELAY,431 ],432 ];433434 const feeItem = 0;435436 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438 balanceBobFinal = await helper.balance.getSubstrate(bob.address);439 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441 await usingRelayPlaygrounds(relayUrl, async (helper) => {442 await helper.wait.newBlocks(10);443 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448 });449 });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {453 let alice: IKeyringPair;454 let randomAccount: IKeyringPair;455456 let balanceQuartzTokenInit: bigint;457 let balanceQuartzTokenMiddle: bigint;458 let balanceQuartzTokenFinal: bigint;459 let balanceKaruraTokenInit: bigint;460 let balanceKaruraTokenMiddle: bigint;461 let balanceKaruraTokenFinal: bigint;462 let balanceQuartzForeignTokenInit: bigint;463 let balanceQuartzForeignTokenMiddle: bigint;464 let balanceQuartzForeignTokenFinal: bigint;465466 // computed by a test transfer from prod Quartz to prod Karura.467 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9468 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)469 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;470471 const KARURA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedKaruraIncomeFee;472473 before(async () => {474 await usingPlaygrounds(async (helper, privateKey) => {475 alice = await privateKey('//Alice');476 [randomAccount] = await helper.arrange.createAccounts([0n], alice);477478 // Set the default version to wrap the first message to other chains.479 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);480 });481482 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {483 const destination = {484 V1: {485 parents: 1,486 interior: {487 X1: {488 Parachain: QUARTZ_CHAIN,489 },490 },491 },492 };493494 const metadata = {495 name: 'Quartz',496 symbol: 'QTZ',497 decimals: 18,498 minimalBalance: 1000000000000000000n,499 };500501 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);503 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);504 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});505 });506507 await usingPlaygrounds(async (helper) => {508 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);509 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);510 });511 });512513 itSub('Should connect and send QTZ to Karura', async ({helper}) => {514 const destination = {515 V2: {516 parents: 1,517 interior: {518 X1: {519 Parachain: KARURA_CHAIN,520 },521 },522 },523 };524525 const beneficiary = {526 V2: {527 parents: 0,528 interior: {529 X1: {530 AccountId32: {531 network: 'Any',532 id: randomAccount.addressRaw,533 },534 },535 },536 },537 };538539 const assets = {540 V2: [541 {542 id: {543 Concrete: {544 parents: 0,545 interior: 'Here',546 },547 },548 fun: {549 Fungible: TRANSFER_AMOUNT,550 },551 },552 ],553 };554555 const feeAssetItem = 0;556557 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');558 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);559560 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;561 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;562 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));563564 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {565 await helper.wait.newBlocks(3);566567 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});568 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);569570 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;571 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;572 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;573574 console.log(575 '[Quartz -> Karura] transaction fees on Karura: %s KAR',576 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),577 );578 console.log(579 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',580 helper.util.bigIntToDecimals(karUnqFees),581 );582 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));583 expect(karFees == 0n).to.be.true;584 expect(585 karUnqFees == expectedKaruraIncomeFee,586 'Karura took different income fee, check the Karura foreign asset config',587 ).to.be.true;588 });589 });590591 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {592 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {593 const destination = {594 V1: {595 parents: 1,596 interior: {597 X2: [598 {Parachain: QUARTZ_CHAIN},599 {600 AccountId32: {601 network: 'Any',602 id: randomAccount.addressRaw,603 },604 },605 ],606 },607 },608 };609610 const id = {611 ForeignAsset: 0,612 };613614 await helper.xTokens.transfer(randomAccount, id, KARURA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');615 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);616 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);617618 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;619 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;620621 console.log(622 '[Karura -> Quartz] transaction fees on Karura: %s KAR',623 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),624 );625 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));626627 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;628 expect(qtzOutcomeTransfer == KARURA_BACKWARD_TRANSFER_AMOUNT).to.be.true;629 });630631 await helper.wait.newBlocks(3);632633 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);634 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;635 expect(actuallyDelivered > 0).to.be.true;636637 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));638639 const qtzFees = KARURA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;640 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));641 expect(qtzFees == 0n).to.be.true;642 });643644 itSub('Karura can send only up to its balance', async ({helper}) => {645 // set Karura's sovereign account's balance646 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);647 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);648 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);649650 const moreThanKaruraHas = karuraBalance * 2n;651652 let targetAccountBalance = 0n;653 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);654655 const quartzMultilocation = {656 V1: {657 parents: 1,658 interior: {659 X1: {Parachain: QUARTZ_CHAIN},660 },661 },662 };663664 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665 targetAccount.addressRaw,666 {667 Concrete: {668 parents: 0,669 interior: 'Here',670 },671 },672 moreThanKaruraHas,673 );674675 let maliciousXcmProgramSent: any;676 const maxWaitBlocks = 3;677678 // Try to trick Quartz679 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {680 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);681682 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683 });684685 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {686 return event.messageHash() == maliciousXcmProgramSent.messageHash()687 && event.outcome().isFailedToTransactAsset;688 });689690 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);691 expect(targetAccountBalance).to.be.equal(0n);692693 // But Karura still can send the correct amount694 const validTransferAmount = karuraBalance / 2n;695 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(696 targetAccount.addressRaw,697 {698 Concrete: {699 parents: 0,700 interior: 'Here',701 },702 },703 validTransferAmount,704 );705706 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {707 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);708 });709710 await helper.wait.newBlocks(maxWaitBlocks);711712 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);713 expect(targetAccountBalance).to.be.equal(validTransferAmount);714 });715716 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {717 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);718 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);719720 const quartzMultilocation = {721 V1: {722 parents: 1,723 interior: {724 X1: {725 Parachain: QUARTZ_CHAIN,726 },727 },728 },729 };730731 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(732 targetAccount.addressRaw,733 {734 Concrete: {735 parents: 1,736 interior: {737 X1: {738 Parachain: QUARTZ_CHAIN,739 },740 },741 },742 },743 testAmount,744 );745746 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(747 targetAccount.addressRaw,748 {749 Concrete: {750 parents: 0,751 interior: 'Here',752 },753 },754 testAmount,755 );756757 let maliciousXcmProgramFullIdSent: any;758 let maliciousXcmProgramHereIdSent: any;759 const maxWaitBlocks = 3;760761 // Try to trick Quartz using full QTZ identification762 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {763 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);764765 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);766 });767768 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {769 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()770 && event.outcome().isUntrustedReserveLocation;771 });772773 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);774 expect(accountBalance).to.be.equal(0n);775776 // Try to trick Quartz using shortened QTZ identification777 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {778 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);779780 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);781 });782783 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {784 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()785 && event.outcome().isUntrustedReserveLocation;786 });787788 accountBalance = await helper.balance.getSubstrate(targetAccount.address);789 expect(accountBalance).to.be.equal(0n);790 });791});792793// These tests are relevant only when794// the the corresponding foreign assets are not registered795describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {796 let alice: IKeyringPair;797 let alith: IKeyringPair;798799 const testAmount = 100_000_000_000n;800 let quartzParachainJunction;801 let quartzAccountJunction;802803 let quartzParachainMultilocation: any;804 let quartzAccountMultilocation: any;805 let quartzCombinedMultilocation: any;806807 let messageSent: any;808809 const maxWaitBlocks = 3;810811 before(async () => {812 await usingPlaygrounds(async (helper, privateKey) => {813 alice = await privateKey('//Alice');814815 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};816 quartzAccountJunction = {817 AccountId32: {818 network: 'Any',819 id: alice.addressRaw,820 },821 };822823 quartzParachainMultilocation = {824 V1: {825 parents: 1,826 interior: {827 X1: quartzParachainJunction,828 },829 },830 };831832 quartzAccountMultilocation = {833 V1: {834 parents: 0,835 interior: {836 X1: quartzAccountJunction,837 },838 },839 };840841 quartzCombinedMultilocation = {842 V1: {843 parents: 1,844 interior: {845 X2: [quartzParachainJunction, quartzAccountJunction],846 },847 },848 };849850 // Set the default version to wrap the first message to other chains.851 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);852 });853854 // eslint-disable-next-line require-await855 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {856 alith = helper.account.alithAccount();857 });858 });859860 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {861 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {862 return event.messageHash() == messageSent.messageHash()863 && event.outcome().isFailedToTransactAsset;864 });865 };866867 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {868 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {869 const id = {870 Token: 'KAR',871 };872 const destination = quartzCombinedMultilocation;873 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');874875 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);876 });877878 await expectFailedToTransact(helper, messageSent);879 });880881 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {882 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {883 const id = 'SelfReserve';884 const destination = quartzCombinedMultilocation;885 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');886887 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);888 });889890 await expectFailedToTransact(helper, messageSent);891 });892893 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {894 await usingShidenPlaygrounds(shidenUrl, async (helper) => {895 const destinationParachain = quartzParachainMultilocation;896 const beneficiary = quartzAccountMultilocation;897 const assets = {898 V1: [{899 id: {900 Concrete: {901 parents: 0,902 interior: 'Here',903 },904 },905 fun: {906 Fungible: testAmount,907 },908 }],909 };910 const feeAssetItem = 0;911912 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [913 destinationParachain,914 beneficiary,915 assets,916 feeAssetItem,917 ]);918919 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);920 });921922 await expectFailedToTransact(helper, messageSent);923 });924});925926describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {927 // Quartz constants928 let alice: IKeyringPair;929 let quartzAssetLocation;930931 let randomAccountQuartz: IKeyringPair;932 let randomAccountMoonriver: IKeyringPair;933934 // Moonriver constants935 let assetId: string;936937 const quartzAssetMetadata = {938 name: 'xcQuartz',939 symbol: 'xcQTZ',940 decimals: 18,941 isFrozen: false,942 minimalBalance: 1n,943 };944945 let balanceQuartzTokenInit: bigint;946 let balanceQuartzTokenMiddle: bigint;947 let balanceQuartzTokenFinal: bigint;948 let balanceForeignQtzTokenInit: bigint;949 let balanceForeignQtzTokenMiddle: bigint;950 let balanceForeignQtzTokenFinal: bigint;951 let balanceMovrTokenInit: bigint;952 let balanceMovrTokenMiddle: bigint;953 let balanceMovrTokenFinal: bigint;954955 before(async () => {956 await usingPlaygrounds(async (helper, privateKey) => {957 alice = await privateKey('//Alice');958 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);959960 balanceForeignQtzTokenInit = 0n;961962 // Set the default version to wrap the first message to other chains.963 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);964 });965966 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {967 const alithAccount = helper.account.alithAccount();968 const baltatharAccount = helper.account.baltatharAccount();969 const dorothyAccount = helper.account.dorothyAccount();970971 randomAccountMoonriver = helper.account.create();972973 // >>> Sponsoring Dorothy >>>974 console.log('Sponsoring Dorothy.......');975 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);976 console.log('Sponsoring Dorothy.......DONE');977 // <<< Sponsoring Dorothy <<<978979 quartzAssetLocation = {980 XCM: {981 parents: 1,982 interior: {X1: {Parachain: QUARTZ_CHAIN}},983 },984 };985 const existentialDeposit = 1n;986 const isSufficient = true;987 const unitsPerSecond = 1n;988 const numAssetsWeightHint = 0;989990 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({991 location: quartzAssetLocation,992 metadata: quartzAssetMetadata,993 existentialDeposit,994 isSufficient,995 unitsPerSecond,996 numAssetsWeightHint,997 });998999 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10001001 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);10021003 // >>> Acquire Quartz AssetId Info on Moonriver >>>1004 console.log('Acquire Quartz AssetId Info on Moonriver.......');10051006 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10071008 console.log('QTZ asset ID is %s', assetId);1009 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1010 // >>> Acquire Quartz AssetId Info on Moonriver >>>10111012 // >>> Sponsoring random Account >>>1013 console.log('Sponsoring random Account.......');1014 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1015 console.log('Sponsoring random Account.......DONE');1016 // <<< Sponsoring random Account <<<10171018 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1019 });10201021 await usingPlaygrounds(async (helper) => {1022 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1023 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1024 });1025 });10261027 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1028 const currencyId = {1029 NativeAssetId: 'Here',1030 };1031 const dest = {1032 V2: {1033 parents: 1,1034 interior: {1035 X2: [1036 {Parachain: MOONRIVER_CHAIN},1037 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1038 ],1039 },1040 },1041 };1042 const amount = TRANSFER_AMOUNT;10431044 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10451046 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1047 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10481049 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1050 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1051 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10521053 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1054 await helper.wait.newBlocks(3);10551056 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10571058 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1059 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1060 expect(movrFees == 0n).to.be.true;10611062 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1063 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1064 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1065 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1066 });1067 });10681069 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1070 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1071 const asset = {1072 V1: {1073 id: {1074 Concrete: {1075 parents: 1,1076 interior: {1077 X1: {Parachain: QUARTZ_CHAIN},1078 },1079 },1080 },1081 fun: {1082 Fungible: TRANSFER_AMOUNT,1083 },1084 },1085 };1086 const destination = {1087 V1: {1088 parents: 1,1089 interior: {1090 X2: [1091 {Parachain: QUARTZ_CHAIN},1092 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1093 ],1094 },1095 },1096 };10971098 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10991100 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);11011102 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1103 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1104 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;11051106 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11071108 expect(qtzRandomAccountAsset).to.be.null;11091110 balanceForeignQtzTokenFinal = 0n;11111112 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1113 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1114 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1115 });11161117 await helper.wait.newBlocks(3);11181119 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1120 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1121 expect(actuallyDelivered > 0).to.be.true;11221123 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11241125 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1126 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1127 expect(qtzFees == 0n).to.be.true;1128 });11291130 itSub('Moonriver can send only up to its balance', async ({helper}) => {1131 // set Moonriver's sovereign account's balance1132 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1133 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1134 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11351136 const moreThanMoonriverHas = moonriverBalance * 2n;11371138 let targetAccountBalance = 0n;1139 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11401141 const quartzMultilocation = {1142 V1: {1143 parents: 1,1144 interior: {1145 X1: {Parachain: QUARTZ_CHAIN},1146 },1147 },1148 };11491150 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1151 targetAccount.addressRaw,1152 {1153 Concrete: {1154 parents: 0,1155 interior: 'Here',1156 },1157 },1158 moreThanMoonriverHas,1159 );11601161 let maliciousXcmProgramSent: any;1162 const maxWaitBlocks = 3;11631164 // Try to trick Quartz1165 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1166 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11671168 // Needed to bypass the call filter.1169 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1170 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11711172 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1173 });11741175 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1176 return event.messageHash() == maliciousXcmProgramSent.messageHash()1177 && event.outcome().isFailedToTransactAsset;1178 });11791180 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1181 expect(targetAccountBalance).to.be.equal(0n);11821183 // But Moonriver still can send the correct amount1184 const validTransferAmount = moonriverBalance / 2n;1185 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1186 targetAccount.addressRaw,1187 {1188 Concrete: {1189 parents: 0,1190 interior: 'Here',1191 },1192 },1193 validTransferAmount,1194 );11951196 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1197 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11981199 // Needed to bypass the call filter.1200 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1201 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1202 });12031204 await helper.wait.newBlocks(maxWaitBlocks);12051206 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1207 expect(targetAccountBalance).to.be.equal(validTransferAmount);1208 });12091210 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1211 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1212 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12131214 const quartzMultilocation = {1215 V1: {1216 parents: 1,1217 interior: {1218 X1: {1219 Parachain: QUARTZ_CHAIN,1220 },1221 },1222 },1223 };12241225 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1226 targetAccount.addressRaw,1227 {1228 Concrete: {1229 parents: 0,1230 interior: {1231 X1: {1232 Parachain: QUARTZ_CHAIN,1233 },1234 },1235 },1236 },1237 testAmount,1238 );12391240 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1241 targetAccount.addressRaw,1242 {1243 Concrete: {1244 parents: 0,1245 interior: 'Here',1246 },1247 },1248 testAmount,1249 );12501251 let maliciousXcmProgramFullIdSent: any;1252 let maliciousXcmProgramHereIdSent: any;1253 const maxWaitBlocks = 3;12541255 // Try to trick Quartz using full QTZ identification1256 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1257 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12581259 // Needed to bypass the call filter.1260 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1261 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12621263 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1264 });12651266 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1267 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1268 && event.outcome().isUntrustedReserveLocation;1269 });12701271 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1272 expect(accountBalance).to.be.equal(0n);12731274 // Try to trick Quartz using shortened QTZ identification1275 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1276 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12771278 // Needed to bypass the call filter.1279 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1280 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12811282 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1283 });12841285 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1286 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1287 && event.outcome().isUntrustedReserveLocation;1288 });12891290 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1291 expect(accountBalance).to.be.equal(0n);1292 });1293});12941295describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1296 let alice: IKeyringPair;1297 let sender: IKeyringPair;12981299 const QTZ_ASSET_ID_ON_SHIDEN = 1;1300 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;13011302 // Quartz -> Shiden1303 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1304 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1305 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1306 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens13071308 // Shiden -> Quartz1309 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1310 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ13111312 let balanceAfterQuartzToShidenXCM: bigint;13131314 before(async () => {1315 await usingPlaygrounds(async (helper, privateKey) => {1316 alice = await privateKey('//Alice');1317 [sender] = await helper.arrange.createAccounts([100n], alice);1318 console.log('sender', sender.address);13191320 // Set the default version to wrap the first message to other chains.1321 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1322 });13231324 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1325 console.log('1. Create foreign asset and metadata');1326 // TODO update metadata with values from production1327 await helper.assets.create(1328 alice,1329 QTZ_ASSET_ID_ON_SHIDEN,1330 alice.address,1331 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1332 );13331334 await helper.assets.setMetadata(1335 alice,1336 QTZ_ASSET_ID_ON_SHIDEN,1337 'Cross chain QTZ',1338 'xcQTZ',1339 Number(QTZ_DECIMALS),1340 );13411342 console.log('2. Register asset location on Shiden');1343 const assetLocation = {1344 V1: {1345 parents: 1,1346 interior: {1347 X1: {1348 Parachain: QUARTZ_CHAIN,1349 },1350 },1351 },1352 };13531354 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13551356 console.log('3. Set QTZ payment for XCM execution on Shiden');1357 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13581359 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1360 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1361 });1362 });13631364 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1365 const destination = {1366 V2: {1367 parents: 1,1368 interior: {1369 X1: {1370 Parachain: SHIDEN_CHAIN,1371 },1372 },1373 },1374 };13751376 const beneficiary = {1377 V2: {1378 parents: 0,1379 interior: {1380 X1: {1381 AccountId32: {1382 network: 'Any',1383 id: sender.addressRaw,1384 },1385 },1386 },1387 },1388 };13891390 const assets = {1391 V2: [1392 {1393 id: {1394 Concrete: {1395 parents: 0,1396 interior: 'Here',1397 },1398 },1399 fun: {1400 Fungible: qtzToShidenTransferred,1401 },1402 },1403 ],1404 };14051406 // Initial balance is 100 QTZ1407 const balanceBefore = await helper.balance.getSubstrate(sender.address);1408 console.log(`Initial balance is: ${balanceBefore}`);14091410 const feeAssetItem = 0;1411 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14121413 // Balance after reserve transfer is less than 901414 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1415 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1416 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1417 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14181419 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1420 await helper.wait.newBlocks(3);1421 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1422 const shidenBalance = await helper.balance.getSubstrate(sender.address);14231424 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1425 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14261427 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1428 // SHD balance does not changed:1429 expect(shidenBalance).to.eq(shidenInitialBalance);1430 });1431 });14321433 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1434 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1435 const destination = {1436 V1: {1437 parents: 1,1438 interior: {1439 X1: {1440 Parachain: QUARTZ_CHAIN,1441 },1442 },1443 },1444 };14451446 const beneficiary = {1447 V1: {1448 parents: 0,1449 interior: {1450 X1: {1451 AccountId32: {1452 network: 'Any',1453 id: sender.addressRaw,1454 },1455 },1456 },1457 },1458 };14591460 const assets = {1461 V1: [1462 {1463 id: {1464 Concrete: {1465 parents: 1,1466 interior: {1467 X1: {1468 Parachain: QUARTZ_CHAIN,1469 },1470 },1471 },1472 },1473 fun: {1474 Fungible: qtzFromShidenTransfered,1475 },1476 },1477 ],1478 };14791480 // Initial balance is 1 SDN1481 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1482 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1483 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14841485 const feeAssetItem = 0;1486 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1487 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14881489 // Balance after reserve transfer is less than 1 SDN1490 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1491 const balanceSDN = await helper.balance.getSubstrate(sender.address);1492 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14931494 // Assert: xcQTZ balance correctly decreased1495 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1496 // Assert: SDN balance is 0.996...1497 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1498 });14991500 await helper.wait.newBlocks(3);1501 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1502 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1503 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1504 });15051506 itSub('Shiden can send only up to its balance', async ({helper}) => {1507 // set Shiden's sovereign account's balance1508 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1509 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1510 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15111512 const moreThanShidenHas = shidenBalance * 2n;15131514 let targetAccountBalance = 0n;1515 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15161517 const quartzMultilocation = {1518 V1: {1519 parents: 1,1520 interior: {1521 X1: {Parachain: QUARTZ_CHAIN},1522 },1523 },1524 };15251526 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1527 targetAccount.addressRaw,1528 {1529 Concrete: {1530 parents: 0,1531 interior: 'Here',1532 },1533 },1534 moreThanShidenHas,1535 );15361537 let maliciousXcmProgramSent: any;1538 const maxWaitBlocks = 3;15391540 // Try to trick Quartz1541 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1542 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15431544 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1545 });15461547 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1548 return event.messageHash() == maliciousXcmProgramSent.messageHash()1549 && event.outcome().isFailedToTransactAsset;1550 });15511552 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1553 expect(targetAccountBalance).to.be.equal(0n);15541555 // But Shiden still can send the correct amount1556 const validTransferAmount = shidenBalance / 2n;1557 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1558 targetAccount.addressRaw,1559 {1560 Concrete: {1561 parents: 0,1562 interior: 'Here',1563 },1564 },1565 validTransferAmount,1566 );15671568 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1569 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1570 });15711572 await helper.wait.newBlocks(maxWaitBlocks);15731574 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1575 expect(targetAccountBalance).to.be.equal(validTransferAmount);1576 });15771578 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1579 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1580 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15811582 const quartzMultilocation = {1583 V1: {1584 parents: 1,1585 interior: {1586 X1: {1587 Parachain: QUARTZ_CHAIN,1588 },1589 },1590 },1591 };15921593 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1594 targetAccount.addressRaw,1595 {1596 Concrete: {1597 parents: 1,1598 interior: {1599 X1: {1600 Parachain: QUARTZ_CHAIN,1601 },1602 },1603 },1604 },1605 testAmount,1606 );16071608 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1609 targetAccount.addressRaw,1610 {1611 Concrete: {1612 parents: 0,1613 interior: 'Here',1614 },1615 },1616 testAmount,1617 );16181619 let maliciousXcmProgramFullIdSent: any;1620 let maliciousXcmProgramHereIdSent: any;1621 const maxWaitBlocks = 3;16221623 // Try to trick Quartz using full QTZ identification1624 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1625 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16261627 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1628 });16291630 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1631 return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1632 && event.outcome().isUntrustedReserveLocation;1633 });16341635 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1636 expect(accountBalance).to.be.equal(0n);16371638 // Try to trick Quartz using shortened QTZ identification1639 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1640 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16411642 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1643 });16441645 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1646 return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1647 && event.outcome().isUntrustedReserveLocation;1648 });16491650 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1651 expect(accountBalance).to.be.equal(0n);1652 });1653});tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -16,9 +16,8 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {XcmV2TraitsError} from '../interfaces';
import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';
-import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
const UNIQUE_CHAIN = 2037;
const STATEMINT_CHAIN = 1000;
@@ -675,30 +674,20 @@
moreThanAcalaHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+
+ maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- const maxWaitBlocks = 3;
- const outcomeField = 1;
-
- const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
-
- expect(
- xcmpQueueFailEvent!.isFailedToTransactAsset,
- `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramSent.messageHash()
+ && event.outcome().isFailedToTransactAsset;
+ });
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -767,30 +756,21 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique using full UNQ identification
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
- });
- const maxWaitBlocks = 3;
- const outcomeField = 1;
-
- let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
+ maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
-
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -798,24 +778,14 @@
// Try to trick Unique using shortened UNQ identification
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
- });
- xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
+ maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -836,6 +806,10 @@
let uniqueAccountMultilocation: any;
let uniqueCombinedMultilocation: any;
+ let messageSent: any;
+
+ const maxWaitBlocks = 3;
+
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
@@ -885,26 +859,11 @@
});
});
- const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {
- const maxWaitBlocks = 3;
- const outcomeField = 1;
-
- const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- `[reject ${network} tokens] 'xcmpQueue.FailEvent' event is expected`,
- ).to.be.true;
-
- expect(
- xcmpQueueFailEvent!.isFailedToTransactAsset,
- `[reject ${network} tokens] The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == messageSent.messageHash()
+ && event.outcome().isFailedToTransactAsset;
+ });
};
itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {
@@ -914,9 +873,11 @@
};
const destination = uniqueCombinedMultilocation;
await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('ACA', helper);
+ await expectFailedToTransact(helper, messageSent);
});
itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {
@@ -924,9 +885,11 @@
const id = 'SelfReserve';
const destination = uniqueCombinedMultilocation;
await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('GLMR', helper);
+ await expectFailedToTransact(helper, messageSent);
});
itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {
@@ -954,9 +917,11 @@
assets,
feeAssetItem,
]);
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('ASTR', helper);
+ await expectFailedToTransact(helper, messageSent);
});
});
@@ -1196,6 +1161,9 @@
moreThanMoonbeamHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
@@ -1203,27 +1171,14 @@
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);
- });
- const maxWaitBlocks = 3;
- const outcomeField = 1;
-
- const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
+ maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent!.isFailedToTransactAsset,
- `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramSent.messageHash()
+ && event.outcome().isFailedToTransactAsset;
+ });
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -1296,6 +1251,10 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique using full UNQ identification
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
@@ -1303,27 +1262,14 @@
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);
- });
-
- const maxWaitBlocks = 3;
- const outcomeField = 1;
- let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
+ maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1335,24 +1281,14 @@
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);
- });
- xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
+ maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1600,31 +1536,21 @@
moreThanAstarHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique
await usingAstarPlaygrounds(astarUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+
+ maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- const maxWaitBlocks = 3;
- const outcomeField = 1;
-
- const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramSent.messageHash()
+ && event.outcome().isFailedToTransactAsset;
+ });
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
-
- expect(
- xcmpQueueFailEvent!.isFailedToTransactAsset,
- `The XCM error should be 'FailedToTransactAsset', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
-
targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(targetAccountBalance).to.be.equal(0n);
@@ -1692,30 +1618,21 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Unique using full UNQ identification
await usingAstarPlaygrounds(astarUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
- });
- const maxWaitBlocks = 3;
- const outcomeField = 1;
+ maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
-
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);
@@ -1723,24 +1640,14 @@
// Try to trick Unique using shortened UNQ identification
await usingAstarPlaygrounds(astarUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
- });
- xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
- maxWaitBlocks,
- 'xcmpQueue',
- 'Fail',
- outcomeField,
- );
-
- expect(
- xcmpQueueFailEvent != null,
- '\'xcmpQueue.FailEvent\' event is expected',
- ).to.be.true;
+ maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
+ });
- expect(
- xcmpQueueFailEvent!.isUntrustedReserveLocation,
- `The XCM error should be 'UntrustedReserveLocation', got '${xcmpQueueFailEvent!.toHuman()}'`,
- ).to.be.true;
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {
+ return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()
+ && event.outcome().isUntrustedReserveLocation;
+ });
accountBalance = await helper.balance.getSubstrate(targetAccount.address);
expect(accountBalance).to.be.equal(0n);