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.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, IPovInfo, TSigner} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';1617export class SilentLogger {18 log(_msg: any, _level: any): void { }19 level = {20 ERROR: 'ERROR' as const,21 WARNING: 'WARNING' as const,22 INFO: 'INFO' as const,23 };24}2526export class SilentConsole {27 // TODO: Remove, this is temporary: Filter unneeded API output28 // (Jaco promised it will be removed in the next version)29 consoleErr: any;30 consoleLog: any;31 consoleWarn: any;3233 constructor() {34 this.consoleErr = console.error;35 this.consoleLog = console.log;36 this.consoleWarn = console.warn;37 }3839 enable() {40 const outFn = (printer: any) => (...args: any[]) => {41 for (const arg of args) {42 if (typeof arg !== 'string')43 continue;44 if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')45 return;46 }47 printer(...args);48 };4950 console.error = outFn(this.consoleErr.bind(console));51 console.log = outFn(this.consoleLog.bind(console));52 console.warn = outFn(this.consoleWarn.bind(console));53 }5455 disable() {56 console.error = this.consoleErr;57 console.log = this.consoleLog;58 console.warn = this.consoleWarn;59 }60}6162export class DevUniqueHelper extends UniqueHelper {63 /**64 * Arrange methods for tests65 */66 arrange: ArrangeGroup;67 wait: WaitGroup;68 admin: AdminGroup;69 session: SessionGroup;70 testUtils: TestUtilGroup;7172 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {73 options.helperBase = options.helperBase ?? DevUniqueHelper;7475 super(logger, options);76 this.arrange = new ArrangeGroup(this);77 this.wait = new WaitGroup(this);78 this.admin = new AdminGroup(this);79 this.testUtils = new TestUtilGroup(this);80 this.session = new SessionGroup(this);81 }8283 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {84 const wsProvider = new WsProvider(wsEndpoint);85 this.api = new ApiPromise({86 provider: wsProvider,87 signedExtensions: {88 ContractHelpers: {89 extrinsic: {},90 payload: {},91 },92 CheckMaintenance: {93 extrinsic: {},94 payload: {},95 },96 DisableIdentityCalls: {97 extrinsic: {},98 payload: {},99 },100 FakeTransactionFinalizer: {101 extrinsic: {},102 payload: {},103 },104 },105 rpc: {106 unique: defs.unique.rpc,107 appPromotion: defs.appPromotion.rpc,108 povinfo: defs.povinfo.rpc,109 eth: {110 feeHistory: {111 description: 'Dummy',112 params: [],113 type: 'u8',114 },115 maxPriorityFeePerGas: {116 description: 'Dummy',117 params: [],118 type: 'u8',119 },120 },121 },122 });123 await this.api.isReadyOrError;124 this.network = await UniqueHelper.detectNetwork(this.api);125 this.wsEndpoint = wsEndpoint;126 }127}128129export class DevRelayHelper extends RelayHelper {130 wait: WaitGroup;131132 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {133 options.helperBase = options.helperBase ?? DevRelayHelper;134135 super(logger, options);136 this.wait = new WaitGroup(this);137 }138}139140export class DevWestmintHelper extends WestmintHelper {141 wait: WaitGroup;142143 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {144 options.helperBase = options.helperBase ?? DevWestmintHelper;145146 super(logger, options);147 this.wait = new WaitGroup(this);148 }149}150151export class DevStatemineHelper extends DevWestmintHelper {}152153export class DevStatemintHelper extends DevWestmintHelper {}154155export class DevMoonbeamHelper extends MoonbeamHelper {156 account: MoonbeamAccountGroup;157 wait: WaitGroup;158 fastDemocracy: MoonbeamFastDemocracyGroup;159160 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {161 options.helperBase = options.helperBase ?? DevMoonbeamHelper;162 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';163164 super(logger, options);165 this.account = new MoonbeamAccountGroup(this);166 this.wait = new WaitGroup(this);167 this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);168 }169}170171export class DevMoonriverHelper extends DevMoonbeamHelper {172 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {173 options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';174 super(logger, options);175 }176}177178export class DevAstarHelper extends AstarHelper {179 wait: WaitGroup;180181 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {182 options.helperBase = options.helperBase ?? DevAstarHelper;183184 super(logger, options);185 this.wait = new WaitGroup(this);186 }187}188189export class DevShidenHelper extends AstarHelper {190 wait: WaitGroup;191192 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {193 options.helperBase = options.helperBase ?? DevShidenHelper;194195 super(logger, options);196 this.wait = new WaitGroup(this);197 }198}199200export class DevAcalaHelper extends AcalaHelper {201 wait: WaitGroup;202203 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {204 options.helperBase = options.helperBase ?? DevAcalaHelper;205206 super(logger, options);207 this.wait = new WaitGroup(this);208 }209}210211export class DevKaruraHelper extends DevAcalaHelper {}212213export class ArrangeGroup {214 helper: DevUniqueHelper;215216 scheduledIdSlider = 0;217218 constructor(helper: DevUniqueHelper) {219 this.helper = helper;220 }221222 /**223 * Generates accounts with the specified UNQ token balance224 * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.225 * @param donor donor account for balances226 * @returns array of newly created accounts227 * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);228 */229 createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {230 let nonce = await this.helper.chain.getNonce(donor.address);231 const wait = new WaitGroup(this.helper);232 const ss58Format = this.helper.chain.getChainProperties().ss58Format;233 const tokenNominal = this.helper.balance.getOneTokenNominal();234 const transactions = [];235 const accounts: IKeyringPair[] = [];236 for (const balance of balances) {237 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);238 accounts.push(recipient);239 if (balance !== 0n) {240 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);241 transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));242 nonce++;243 }244 }245246 await Promise.all(transactions).catch(_e => {});247248 //#region TODO remove this region, when nonce problem will be solved249 const checkBalances = async () => {250 let isSuccess = true;251 for (let i = 0; i < balances.length; i++) {252 const balance = await this.helper.balance.getSubstrate(accounts[i].address);253 if (balance !== balances[i] * tokenNominal) {254 isSuccess = false;255 break;256 }257 }258 return isSuccess;259 };260261 let accountsCreated = false;262 const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;263 // checkBalances retry up to 5-50 blocks264 for (let index = 0; index < maxBlocksChecked; index++) {265 accountsCreated = await checkBalances();266 if(accountsCreated) break;267 await wait.newBlocks(1);268 }269270 if (!accountsCreated) throw Error('Accounts generation failed');271 //#endregion272273 return accounts;274 };275276 // TODO combine this method and createAccounts into one277 createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {278 const createAsManyAsCan = async () => {279 let transactions: any = [];280 const accounts: IKeyringPair[] = [];281 let nonce = await this.helper.chain.getNonce(donor.address);282 const tokenNominal = this.helper.balance.getOneTokenNominal();283 const ss58Format = this.helper.chain.getChainProperties().ss58Format;284 for (let i = 0; i < accountsToCreate; i++) {285 if (i === 500) { // if there are too many accounts to create286 await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled287 transactions = []; //288 nonce = await this.helper.chain.getNonce(donor.address); // update nonce289 }290 const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);291 accounts.push(recipient);292 if (withBalance !== 0n) {293 const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);294 transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));295 nonce++;296 }297 }298299 const fullfilledAccounts = [];300 await Promise.allSettled(transactions);301 for (const account of accounts) {302 const accountBalance = await this.helper.balance.getSubstrate(account.address);303 if (accountBalance === withBalance * tokenNominal) {304 fullfilledAccounts.push(account);305 }306 }307 return fullfilledAccounts;308 };309310311 const crowd: IKeyringPair[] = [];312 // do up to 5 retries313 for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {314 const asManyAsCan = await createAsManyAsCan();315 crowd.push(...asManyAsCan);316 accountsToCreate -= asManyAsCan.length;317 }318319 if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);320321 return crowd;322 };323324 isDevNode = async () => {325 let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();326 if (blockNumber == 0) {327 await this.helper.wait.newBlocks(1);328 blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();329 }330 const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);331 const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);332 const findCreationDate = (block: any) => {333 const humanBlock = block.toHuman();334 let date;335 humanBlock.block.extrinsics.forEach((ext: any) => {336 if(ext.method.section === 'timestamp') {337 date = Number(ext.method.args.now.replaceAll(',', ''));338 }339 });340 return date;341 };342 const block1date = await findCreationDate(block1);343 const block2date = await findCreationDate(block2);344 if(block2date! - block1date! < 9000) return true;345 };346347 async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {348 const address = payer.Substrate ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum!);349 let balance = await this.helper.balance.getSubstrate(address);350351 await promise();352353 balance -= await this.helper.balance.getSubstrate(address);354355 return balance;356 }357358 async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {359 const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);360361 const kvJson: {[key: string]: string} = {};362363 for (const kv of rawPovInfo.keyValues) {364 kvJson[kv.key.toHex()] = kv.value.toHex();365 }366367 const kvStr = JSON.stringify(kvJson);368369 const chainql = spawnSync(370 'chainql',371 [372 `--tla-code=data=${kvStr}`,373 '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,374 ],375 );376377 if (!chainql.stdout) {378 throw Error('unable to get an output from the `chainql`');379 }380381 return {382 proofSize: rawPovInfo.proofSize.toNumber(),383 compactProofSize: rawPovInfo.compactProofSize.toNumber(),384 compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),385 results: rawPovInfo.results,386 kv: JSON.parse(chainql.stdout.toString()),387 };388 }389390 calculatePalletAddress(palletId: any) {391 const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));392 return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);393 }394395 makeScheduledIds(num: number): string[] {396 function makeId(slider: number) {397 const scheduledIdSize = 64;398 const hexId = slider.toString(16);399 const prefixSize = scheduledIdSize - hexId.length;400401 const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;402403 return scheduledId;404 }405406 const ids = [];407 for (let i = 0; i < num; i++) {408 ids.push(makeId(this.scheduledIdSlider));409 this.scheduledIdSlider += 1;410 }411412 return ids;413 }414415 makeScheduledId(): string {416 return (this.makeScheduledIds(1))[0];417 }418419 async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {420 const capture = new EventCapture(this.helper, eventSection, eventMethod);421 await capture.startCapture();422423 return capture;424 }425426 makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {427 return {428 V2: [429 {430 WithdrawAsset: [431 {432 id,433 fun: {434 Fungible: amount,435 },436 },437 ],438 },439 {440 BuyExecution: {441 fees: {442 id,443 fun: {444 Fungible: amount,445 },446 },447 weightLimit: 'Unlimited',448 },449 },450 {451 DepositAsset: {452 assets: {453 Wild: 'All',454 },455 maxAssets: 1,456 beneficiary: {457 parents: 0,458 interior: {459 X1: {460 AccountId32: {461 network: 'Any',462 id: beneficiary,463 },464 },465 },466 },467 },468 },469 ],470 };471 }472473 makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {474 return {475 V2: [476 {477 ReserveAssetDeposited: [478 {479 id,480 fun: {481 Fungible: amount,482 },483 },484 ],485 },486 {487 BuyExecution: {488 fees: {489 id,490 fun: {491 Fungible: amount,492 },493 },494 weightLimit: 'Unlimited',495 },496 },497 {498 DepositAsset: {499 assets: {500 Wild: 'All',501 },502 maxAssets: 1,503 beneficiary: {504 parents: 0,505 interior: {506 X1: {507 AccountId32: {508 network: 'Any',509 id: beneficiary,510 },511 },512 },513 },514 },515 },516 ],517 };518 }519}520521class MoonbeamAccountGroup {522 helper: MoonbeamHelper;523524 keyring: Keyring;525 _alithAccount: IKeyringPair;526 _baltatharAccount: IKeyringPair;527 _dorothyAccount: IKeyringPair;528529 constructor(helper: MoonbeamHelper) {530 this.helper = helper;531532 this.keyring = new Keyring({type: 'ethereum'});533 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';534 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';535 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';536537 this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');538 this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');539 this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');540 }541542 alithAccount() {543 return this._alithAccount;544 }545546 baltatharAccount() {547 return this._baltatharAccount;548 }549550 dorothyAccount() {551 return this._dorothyAccount;552 }553554 create() {555 return this.keyring.addFromUri(mnemonicGenerate());556 }557}558559class MoonbeamFastDemocracyGroup {560 helper: DevMoonbeamHelper;561562 constructor(helper: DevMoonbeamHelper) {563 this.helper = helper;564 }565566 async executeProposal(proposalDesciption: string, encodedProposal: string) {567 const proposalHash = blake2AsHex(encodedProposal);568569 const alithAccount = this.helper.account.alithAccount();570 const baltatharAccount = this.helper.account.baltatharAccount();571 const dorothyAccount = this.helper.account.dorothyAccount();572573 const councilVotingThreshold = 2;574 const technicalCommitteeThreshold = 2;575 const fastTrackVotingPeriod = 3;576 const fastTrackDelayPeriod = 0;577578 console.log(`[democracy] executing '${proposalDesciption}' proposal`);579580 // >>> Propose external motion through council >>>581 console.log('\t* Propose external motion through council.......');582 const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});583 const encodedMotion = externalMotion?.method.toHex() || '';584 const motionHash = blake2AsHex(encodedMotion);585 console.log('\t* Motion hash is %s', motionHash);586587 await this.helper.collective.council.propose(588 baltatharAccount,589 councilVotingThreshold,590 externalMotion,591 externalMotion.encodedLength,592 );593594 const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;595 await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);596 await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);597598 await this.helper.collective.council.close(599 dorothyAccount,600 motionHash,601 councilProposalIdx,602 {603 refTime: 1_000_000_000,604 proofSize: 1_000_000,605 },606 externalMotion.encodedLength,607 );608 console.log('\t* Propose external motion through council.......DONE');609 // <<< Propose external motion through council <<<610611 // >>> Fast track proposal through technical committee >>>612 console.log('\t* Fast track proposal through technical committee.......');613 const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);614 const encodedFastTrack = fastTrack?.method.toHex() || '';615 const fastTrackHash = blake2AsHex(encodedFastTrack);616 console.log('\t* FastTrack hash is %s', fastTrackHash);617618 await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);619620 const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;621 await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);622 await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);623624 await this.helper.collective.techCommittee.close(625 baltatharAccount,626 fastTrackHash,627 techProposalIdx,628 {629 refTime: 1_000_000_000,630 proofSize: 1_000_000,631 },632 fastTrack.encodedLength,633 );634 console.log('\t* Fast track proposal through technical committee.......DONE');635 // <<< Fast track proposal through technical committee <<<636637 const refIndexField = 0;638 const referendumIndex = await this.helper.wait.eventData<number>(3, 'democracy', 'Started', refIndexField);639640 // >>> Referendum voting >>>641 console.log(`\t* Referendum #${referendumIndex} voting.......`);642 await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex!, {643 balance: 10_000_000_000_000_000_000n,644 vote: {aye: true, conviction: 1},645 });646 console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);647 // <<< Referendum voting <<<648649 // Wait the proposal to pass650 await this.helper.wait.event(3, 'democracy', 'Passed');651 await this.helper.wait.newBlocks(1);652653 console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);654 }655}656657class WaitGroup {658 helper: ChainHelperBase;659660 constructor(helper: ChainHelperBase) {661 this.helper = helper;662 }663664 sleep(milliseconds: number) {665 return new Promise((resolve) => setTimeout(resolve, milliseconds));666 }667668 private async waitWithTimeout(promise: Promise<any>, timeout: number) {669 let isBlock = false;670 promise.then(() => isBlock = true).catch(() => isBlock = true);671 let totalTime = 0;672 const step = 100;673 while(!isBlock) {674 await this.sleep(step);675 totalTime += step;676 if(totalTime >= timeout) throw Error('Blocks production failed');677 }678 return promise;679 }680681 /**682 * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.683 * @param promise async operation to race against the timeout684 * @param timeoutMS time after which to time out685 * @param timeoutError error message to throw686 * @returns promise of the same type the operation had687 */688 withTimeout<T>(689 promise: Promise<T>,690 timeoutMS = 30000,691 timeoutError = 'The operation has timed out!',692 ): Promise<T> {693 const timeout = new Promise<never>((_, reject) => {694 setTimeout(() => {695 reject(new Error(timeoutError));696 }, timeoutMS);697 });698699 return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});700 }701702 /**703 * Wait for specified number of blocks704 * @param blocksCount number of blocks to wait705 * @returns706 */707 async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {708 timeout = timeout ?? blocksCount * 60_000;709 // eslint-disable-next-line no-async-promise-executor710 const promise = new Promise<void>(async (resolve) => {711 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {712 if (blocksCount > 0) {713 blocksCount--;714 } else {715 unsubscribe();716 resolve();717 }718 });719 });720 await this.waitWithTimeout(promise, timeout);721 return promise;722 }723724 /**725 * Wait for the specified number of sessions to pass.726 * Only applicable if the Session pallet is turned on.727 * @param sessionCount number of sessions to wait728 * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks729 * @returns730 */731 async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {732 console.log(`Waiting for ${sessionCount} new session${sessionCount>1's'''}.`733 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');734735 const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;736 let currentSessionIndex = -1;737738 while (currentSessionIndex < expectedSessionIndex) {739 // eslint-disable-next-line no-async-promise-executor740 currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {741 await this.newBlocks(1);742 const res = await (this.helper as DevUniqueHelper).session.getIndex();743 resolve(res);744 }), blockTimeout, 'The chain has stopped producing blocks!');745 }746 }747748 async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {749 timeout = timeout ?? 30 * 60 * 1000;750 // eslint-disable-next-line no-async-promise-executor751 const promise = new Promise<void>(async (resolve) => {752 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {753 if (data.number.toNumber() >= blockNumber) {754 unsubscribe();755 resolve();756 }757 });758 });759 await this.waitWithTimeout(promise, timeout);760 return promise;761 }762763 async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {764 timeout = timeout ?? 30 * 60 * 1000;765 // eslint-disable-next-line no-async-promise-executor766 const promise = new Promise<void>(async (resolve) => {767 const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {768 if (data.value.relayParentNumber.toNumber() >= blockNumber) {769 // @ts-ignore770 unsubscribe();771 resolve();772 }773 });774 });775 await this.waitWithTimeout(promise, timeout);776 return promise;777 }778779 noScheduledTasks() {780 const api = this.helper.getApi();781782 // eslint-disable-next-line no-async-promise-executor783 const promise = new Promise<void>(async resolve => {784 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {785 const areThereScheduledTasks = await api.query.scheduler.lookup.entries();786787 if(areThereScheduledTasks.length == 0) {788 unsubscribe();789 resolve();790 }791 });792 });793794 return promise;795 }796797 event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {798 // eslint-disable-next-line no-async-promise-executor799 const promise = new Promise<EventRecord | null>(async (resolve) => {800 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {801 const blockNumber = header.number.toHuman();802 const blockHash = header.hash;803 const eventIdStr = `${eventSection}.${eventMethod}`;804 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;805806 this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);807808 const apiAt = await this.helper.getApi().at(blockHash);809 const eventRecords = (await apiAt.query.system.events()) as any;810811 const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {812 return r.event.section == eventSection && r.event.method == eventMethod;813 });814815 if (neededEvent) {816 unsubscribe();817 resolve(neededEvent);818 } else if (maxBlocksToWait > 0) {819 maxBlocksToWait--;820 } else {821 this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);822 unsubscribe();823 resolve(null);824 }825 });826 });827 return promise;828 }829830 async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {831 const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);832833 if (eventRecord == null) {834 return null;835 }836837 const event = eventRecord!.event;838 const data = event.data[fieldIndex] as EventT;839840 return data;841 }842}843844class SessionGroup {845 helper: ChainHelperBase;846847 constructor(helper: ChainHelperBase) {848 this.helper = helper;849 }850851 //todo:collator documentation852 async getIndex(): Promise<number> {853 return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();854 }855856 newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {857 return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);858 }859860 setOwnKeys(signer: TSigner, key: string) {861 return this.helper.executeExtrinsic(862 signer,863 'api.tx.session.setKeys',864 [key, '0x0'],865 true,866 );867 }868869 setOwnKeysFromAddress(signer: TSigner) {870 return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));871 }872}873874class TestUtilGroup {875 helper: DevUniqueHelper;876877 constructor(helper: DevUniqueHelper) {878 this.helper = helper;879 }880881 async enable() {882 if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {883 return;884 }885886 const signer = this.helper.util.fromSeed('//Alice');887 await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);888 }889890 async setTestValue(signer: TSigner, testVal: number) {891 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);892 }893894 async incTestValue(signer: TSigner) {895 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);896 }897898 async setTestValueAndRollback(signer: TSigner, testVal: number) {899 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);900 }901902 async testValue(blockIdx?: number) {903 const api = blockIdx904 ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))905 : this.helper.getApi();906907 return (await api.query.testUtils.testValue()).toJSON();908 }909910 async justTakeFee(signer: TSigner) {911 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);912 }913914 async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {915 await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);916 }917}918919class EventCapture {920 helper: DevUniqueHelper;921 eventSection: string;922 eventMethod: string;923 events: EventRecord[] = [];924 unsubscribe: VoidFn | null = null;925926 constructor(927 helper: DevUniqueHelper,928 eventSection: string,929 eventMethod: string,930 ) {931 this.helper = helper;932 this.eventSection = eventSection;933 this.eventMethod = eventMethod;934 }935936 async startCapture() {937 this.stopCapture();938 this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {939 const newEvents = eventRecords.filter(r => {940 return r.event.section == this.eventSection && r.event.method == this.eventMethod;941 });942943 this.events.push(...newEvents);944 })) as any;945 }946947 stopCapture() {948 if (this.unsubscribe !== null) {949 this.unsubscribe();950 }951 }952953 extractCapturedEvents() {954 return this.events;955 }956}957958class AdminGroup {959 helper: UniqueHelper;960961 constructor(helper: UniqueHelper) {962 this.helper = helper;963 }964965 async payoutStakers(signer: IKeyringPair, stakersToPayout: number): Promise<{staker: string, stake: bigint, payout: bigint}[]> {966 const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);967 return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {968 return {969 staker: e.event.data[0].toString(),970 stake: e.event.data[1].toBigInt(),971 payout: e.event.data[2].toBigInt(),972 };973 });974 }975}tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.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, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';
-import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
const QUARTZ_CHAIN = 2095;
const STATEMINE_CHAIN = 1000;
@@ -673,30 +672,20 @@
moreThanKaruraHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz
await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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);
@@ -765,30 +754,21 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz using full QTZ identification
await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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);
@@ -796,24 +776,14 @@
// Try to trick Quartz using shortened QTZ identification
await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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);
@@ -834,6 +804,10 @@
let quartzAccountMultilocation: any;
let quartzCombinedMultilocation: any;
+ let messageSent: any;
+
+ const maxWaitBlocks = 3;
+
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
@@ -883,26 +857,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('Quartz rejects KAR tokens from Karura', async ({helper}) => {
@@ -912,9 +871,11 @@
};
const destination = quartzCombinedMultilocation;
await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('KAR', helper);
+ await expectFailedToTransact(helper, messageSent);
});
itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {
@@ -922,9 +883,11 @@
const id = 'SelfReserve';
const destination = quartzCombinedMultilocation;
await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('MOVR', helper);
+ await expectFailedToTransact(helper, messageSent);
});
itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {
@@ -952,9 +915,11 @@
assets,
feeAssetItem,
]);
+
+ messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
});
- await expectFailedToTransact('SDN', helper);
+ await expectFailedToTransact(helper, messageSent);
});
});
@@ -1193,6 +1158,9 @@
moreThanMoonriverHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz
await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);
@@ -1200,27 +1168,14 @@
// Needed to bypass the call filter.
const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver 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);
@@ -1293,6 +1248,10 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz using full QTZ identification
await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);
@@ -1300,27 +1259,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 QTZ 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 'isUntrustedReserveLocation', 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);
@@ -1332,24 +1278,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 QTZ 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 'isUntrustedReserveLocation', 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);
@@ -1598,31 +1534,21 @@
moreThanShidenHas,
);
+ let maliciousXcmProgramSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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);
@@ -1690,30 +1616,21 @@
testAmount,
);
+ let maliciousXcmProgramFullIdSent: any;
+ let maliciousXcmProgramHereIdSent: any;
+ const maxWaitBlocks = 3;
+
// Try to trick Quartz using full QTZ identification
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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 'isUntrustedReserveLocation', 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);
@@ -1721,24 +1638,14 @@
// Try to trick Quartz using shortened QTZ identification
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
await helper.getSudo().xcm.send(alice, quartzMultilocation, 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 'isUntrustedReserveLocation', 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);
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);