difftreelog
refactor event helpers
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});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);