--- 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); }); }); --- 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(index: number) { + return this.raw().event.data[index].toJSON() as T; + } + + eventData(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(0); + } + + threshold() { + return this.eventJsonData(1); + } + }; + + static Voted = class extends this.Method('Voted') { + voter() { + return this.eventJsonData(0); + } + + referendumIndex() { + return this.eventJsonData(1); + } + + vote() { + return this.eventJsonData(2); + } + }; + + static Passed = class extends this.Method('Passed') { + referendumIndex() { + return this.eventJsonData(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(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(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( + maxBlocksToWait: number, + eventHelperType: new () => T, + filter: (_: T) => boolean = () => { return true; }, + ) { // eslint-disable-next-line no-async-promise-executor - const promise = new Promise(async (resolve) => { + const promise = new Promise(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(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) { - const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod); - - if (eventRecord == null) { - return null; + async expectEvent( + 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; } } --- 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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); --- 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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);