git.delta.rocks / unique-network / refs/commits / 8a18c9958995

difftreelog

refactor event helpers

Daniel Shiposha2023-04-21parent: #6e3cf5e.patch.diff
in: master

4 files changed

modifiedtests/src/scheduler.seqtest.tsdiffbeforeafterboth
before · tests/src/scheduler.seqtest.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {DevUniqueHelper} from './util/playgrounds/unique.dev';2021describe('Scheduling token and balance transfers', () => {22  let superuser: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;25  let charlie: IKeyringPair;2627  before(async function() {28    await usingPlaygrounds(async (helper, privateKey) => {29      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);3031      superuser = await privateKey('//Alice');32      const donor = await privateKey({url: import.meta.url});33      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);3435      await helper.testUtils.enable();36    });37  });3839  beforeEach(async () => {40    await usingPlaygrounds(async (helper) => {41      await helper.wait.noScheduledTasks();42    });43  });4445  itSched('Can delay a transfer of an owned token', async (scheduleKind, {helper}) => {46    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});47    const token = await collection.mintToken(alice);48    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;49    const blocksBeforeExecution = 4;5051    await token.scheduleAfter(blocksBeforeExecution, {scheduledId})52      .transfer(alice, {Substrate: bob.address});53    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;5455    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});5657    await helper.wait.forParachainBlockNumber(executionBlock);5859    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});60  });6162  itSched('Can transfer funds periodically', async (scheduleKind, {helper}) => {63    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;64    const waitForBlocks = 1;6566    const amount = 1n * helper.balance.getOneTokenNominal();67    const periodic = {68      period: 2,69      repetitions: 2,70    };7172    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);7374    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, periodic})75      .balance.transferToSubstrate(alice, bob.address, amount);76    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;7778    await helper.wait.forParachainBlockNumber(executionBlock);7980    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);81    expect(bobsBalanceAfterFirst)82      .to.be.equal(83        bobsBalanceBefore + 1n * amount,84        '#1 Balance of the recipient should be increased by 1 * amount',85      );8687    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);8889    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);90    expect(bobsBalanceAfterSecond)91      .to.be.equal(92        bobsBalanceBefore + 2n * amount,93        '#2 Balance of the recipient should be increased by 2 * amount',94      );95  });9697  itSub('Can cancel a scheduled operation which has not yet taken effect', async ({helper}) => {98    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});99    const token = await collection.mintToken(alice);100101    const scheduledId = helper.arrange.makeScheduledId();102    const waitForBlocks = 4;103104    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});105106    await token.scheduleAfter(waitForBlocks, {scheduledId})107      .transfer(alice, {Substrate: bob.address});108    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;109110    await helper.scheduler.cancelScheduled(alice, scheduledId);111112    await helper.wait.forParachainBlockNumber(executionBlock);113114    expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});115  });116117  itSub('Can cancel a periodic operation (transfer of funds)', async ({helper}) => {118    const waitForBlocks = 1;119    const periodic = {120      period: 3,121      repetitions: 2,122    };123124    const scheduledId = helper.arrange.makeScheduledId();125126    const amount = 1n * helper.balance.getOneTokenNominal();127128    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);129130    await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, periodic})131      .balance.transferToSubstrate(alice, bob.address, amount);132    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;133134    await helper.wait.forParachainBlockNumber(executionBlock);135136    const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address);137138    expect(bobsBalanceAfterFirst)139      .to.be.equal(140        bobsBalanceBefore + 1n * amount,141        '#1 Balance of the recipient should be increased by 1 * amount',142      );143144    await helper.scheduler.cancelScheduled(alice, scheduledId);145    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);146147    const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address);148    expect(bobsBalanceAfterSecond)149      .to.be.equal(150        bobsBalanceAfterFirst,151        '#2 Balance of the recipient should not be changed',152      );153  });154155  itSched('scheduler will not insert more tasks than allowed', async (scheduleKind, {helper}) => {156    const maxScheduledPerBlock = 50;157    let fillScheduledIds = new Array(maxScheduledPerBlock);158    let extraScheduledId = undefined;159160    if (scheduleKind == 'named') {161      const scheduledIds = helper.arrange.makeScheduledIds(maxScheduledPerBlock + 1);162      fillScheduledIds = scheduledIds.slice(0, maxScheduledPerBlock);163      extraScheduledId = scheduledIds[maxScheduledPerBlock];164    }165166    // Since the dev node has Instant Seal,167    // we need a larger gap between the current block and the target one.168    //169    // We will schedule `maxScheduledPerBlock` transaction into the target block,170    // so we need at least `maxScheduledPerBlock`-wide gap.171    // We add some additional blocks to this gap to mitigate possible PolkadotJS delays.172    const waitForBlocks = await helper.arrange.isDevNode() ? maxScheduledPerBlock + 5 : 5;173174    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks;175176    const amount = 1n * helper.balance.getOneTokenNominal();177178    const balanceBefore = await helper.balance.getSubstrate(bob.address);179180    // Fill the target block181    for (let i = 0; i < maxScheduledPerBlock; i++) {182      await helper.scheduler.scheduleAt(executionBlock, {scheduledId: fillScheduledIds[i]})183        .balance.transferToSubstrate(superuser, bob.address, amount);184    }185186    // Try to schedule a task into a full block187    await expect(helper.scheduler.scheduleAt(executionBlock, {scheduledId: extraScheduledId})188      .balance.transferToSubstrate(superuser, bob.address, amount))189      .to.be.rejectedWith(/scheduler\.AgendaIsExhausted/);190191    await helper.wait.forParachainBlockNumber(executionBlock);192193    const balanceAfter = await helper.balance.getSubstrate(bob.address);194195    expect(balanceAfter > balanceBefore).to.be.true;196197    const diff = balanceAfter - balanceBefore;198    expect(diff).to.be.equal(amount * BigInt(maxScheduledPerBlock));199  });200201  itSched.ifWithPallets('Scheduled tasks are transactional', [Pallets.TestUtils], async (scheduleKind, {helper}) => {202    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;203    const waitForBlocks = 4;204205    const initTestVal = 42;206    const changedTestVal = 111;207208    await helper.testUtils.setTestValue(alice, initTestVal);209210    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId})211      .testUtils.setTestValueAndRollback(alice, changedTestVal);212    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;213214    await helper.wait.forParachainBlockNumber(executionBlock);215216    const testVal = await helper.testUtils.testValue();217    expect(testVal, 'The test value should NOT be commited')218      .to.be.equal(initTestVal);219  });220221  itSched.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.TestUtils], async function(scheduleKind, {helper}) {222    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;223    const waitForBlocks = 4;224    const periodic = {225      period: 2,226      repetitions: 2,227    };228229    const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []);230    const scheduledLen = dummyTx.callIndex.length;231232    const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen))233      .partialFee.toBigInt();234235    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId, periodic})236      .testUtils.justTakeFee(alice);237    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;238239    const aliceInitBalance = await helper.balance.getSubstrate(alice.address);240    let diff;241242    await helper.wait.forParachainBlockNumber(executionBlock);243244    const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address);245    expect(246      aliceBalanceAfterFirst < aliceInitBalance,247      '[after execution #1] Scheduled task should take a fee',248    ).to.be.true;249250    diff = aliceInitBalance - aliceBalanceAfterFirst;251    expect(diff).to.be.equal(252      expectedScheduledFee,253      'Scheduled task should take the right amount of fees',254    );255256    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);257258    const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address);259    expect(260      aliceBalanceAfterSecond < aliceBalanceAfterFirst,261      '[after execution #2] Scheduled task should take a fee',262    ).to.be.true;263264    diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond;265    expect(diff).to.be.equal(266      expectedScheduledFee,267      'Scheduled task should take the right amount of fees',268    );269  });270271  // Check if we can cancel a scheduled periodic operation272  // in the same block in which it is running273  itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.TestUtils], async ({helper}) => {274    const currentBlockNumber = await helper.chain.getLatestBlockNumber();275    const blocksBeforeExecution = 10;276    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;277278    const [279      scheduledId,280      scheduledCancelId,281    ] = helper.arrange.makeScheduledIds(2);282283    const periodic = {284      period: 5,285      repetitions: 5,286    };287288    const initTestVal = 0;289    const incTestVal = initTestVal + 1;290    const finalTestVal = initTestVal + 2;291292    await helper.testUtils.setTestValue(alice, initTestVal);293294    await helper.scheduler.scheduleAt<DevUniqueHelper>(firstExecutionBlockNumber, {scheduledId, periodic})295      .testUtils.incTestValue(alice);296297    // Cancel the inc tx after 2 executions298    // *in the same block* in which the second execution is scheduled299    await helper.scheduler.scheduleAt(300      firstExecutionBlockNumber + periodic.period,301      {scheduledId: scheduledCancelId},302    ).scheduler.cancelScheduled(alice, scheduledId);303304    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);305306    // execution #0307    expect(await helper.testUtils.testValue())308      .to.be.equal(incTestVal);309310    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);311312    // execution #1313    expect(await helper.testUtils.testValue())314      .to.be.equal(finalTestVal);315316    for (let i = 1; i < periodic.repetitions; i++) {317      await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1));318      expect(await helper.testUtils.testValue())319        .to.be.equal(finalTestVal);320    }321  });322323  itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.TestUtils], async ({helper}) => {324    const scheduledId = helper.arrange.makeScheduledId();325    const waitForBlocks = 4;326    const periodic = {327      period: 2,328      repetitions: 5,329    };330331    const initTestVal = 0;332    const maxTestVal = 2;333334    await helper.testUtils.setTestValue(alice, initTestVal);335336    await helper.scheduler.scheduleAfter<DevUniqueHelper>(waitForBlocks, {scheduledId, periodic})337      .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal);338    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;339340    await helper.wait.forParachainBlockNumber(executionBlock);341342    // execution #0343    expect(await helper.testUtils.testValue())344      .to.be.equal(initTestVal + 1);345346    await helper.wait.forParachainBlockNumber(executionBlock + periodic.period);347348    // execution #1349    expect(await helper.testUtils.testValue())350      .to.be.equal(initTestVal + 2);351352    await helper.wait.forParachainBlockNumber(executionBlock + 2 * periodic.period);353354    // <canceled>355    expect(await helper.testUtils.testValue())356      .to.be.equal(initTestVal + 2);357  });358359  itSub('Root can cancel any scheduled operation', async ({helper}) => {360    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});361    const token = await collection.mintToken(bob);362363    const scheduledId = helper.arrange.makeScheduledId();364    const waitForBlocks = 4;365366    await token.scheduleAfter(waitForBlocks, {scheduledId})367      .transfer(bob, {Substrate: alice.address});368    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;369370    await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);371372    await helper.wait.forParachainBlockNumber(executionBlock);373374    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});375  });376377  itSched('Root can set prioritized scheduled operation', async (scheduleKind, {helper}) => {378    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;379    const waitForBlocks = 4;380381    const amount = 42n * helper.balance.getOneTokenNominal();382383    const balanceBefore = await helper.balance.getSubstrate(charlie.address);384385    await helper.getSudo()386      .scheduler.scheduleAfter(waitForBlocks, {scheduledId, priority: 42})387      .balance.forceTransferToSubstrate(superuser, bob.address, charlie.address, amount);388    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;389390    await helper.wait.forParachainBlockNumber(executionBlock);391392    const balanceAfter = await helper.balance.getSubstrate(charlie.address);393394    expect(balanceAfter > balanceBefore).to.be.true;395396    const diff = balanceAfter - balanceBefore;397    expect(diff).to.be.equal(amount);398  });399400  itSub("Root can change scheduled operation's priority", async ({helper}) => {401    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});402    const token = await collection.mintToken(bob);403404    const scheduledId = helper.arrange.makeScheduledId();405    const waitForBlocks = 6;406407    await token.scheduleAfter(waitForBlocks, {scheduledId})408      .transfer(bob, {Substrate: alice.address});409    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;410411    const priority = 112;412    await helper.getSudo().scheduler.changePriority(superuser, scheduledId, priority);413414    const priorityChanged = await helper.wait.event(415      waitForBlocks,416      'scheduler',417      'PriorityChanged',418    );419420    expect(priorityChanged !== null).to.be.true;421422    const [blockNumber, index] = priorityChanged!.event.data[0].toJSON() as any[];423    expect(blockNumber).to.be.equal(executionBlock);424    expect(index).to.be.equal(0);425426    expect(priorityChanged!.event.data[1].toString()).to.be.equal(priority.toString());427  });428429  itSub('Prioritized operations execute in valid order', async ({helper}) => {430    const [431      scheduledFirstId,432      scheduledSecondId,433    ] = helper.arrange.makeScheduledIds(2);434435    const currentBlockNumber = await helper.chain.getLatestBlockNumber();436    const blocksBeforeExecution = 6;437    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;438439    const prioHigh = 0;440    const prioLow = 255;441442    const periodic = {443      period: 6,444      repetitions: 2,445    };446447    const amount = 1n * helper.balance.getOneTokenNominal();448449    // Scheduler a task with a lower priority first, then with a higher priority450    await helper.getSudo().scheduler.scheduleAt(firstExecutionBlockNumber, {451      scheduledId: scheduledFirstId,452      priority: prioLow,453      periodic,454    }).balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);455456    await helper.getSudo().scheduler.scheduleAt(firstExecutionBlockNumber, {457      scheduledId: scheduledSecondId,458      priority: prioHigh,459      periodic,460    }).balance.forceTransferToSubstrate(superuser, alice.address, bob.address, amount);461462    const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched');463464    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);465466    // Flip priorities467    await helper.getSudo().scheduler.changePriority(superuser, scheduledFirstId, prioHigh);468    await helper.getSudo().scheduler.changePriority(superuser, scheduledSecondId, prioLow);469470    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period);471472    const dispatchEvents = capture.extractCapturedEvents();473    expect(dispatchEvents.length).to.be.equal(4);474475    const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString());476477    const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]];478    const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]];479480    expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId);481    expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId);482483    expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId);484    expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId);485  });486487  itSched('Periodic operations always can be rescheduled', async (scheduleKind, {helper}) => {488    const maxScheduledPerBlock = 50;489    const numFilledBlocks = 3;490    const ids = helper.arrange.makeScheduledIds(numFilledBlocks * maxScheduledPerBlock + 1);491    const periodicId = scheduleKind == 'named' ? ids[0] : undefined;492    const fillIds = ids.slice(1);493494    const fillScheduleFn = scheduleKind == 'named' ? 'scheduleNamed' : 'schedule';495496    const initTestVal = 0;497    const firstExecTestVal = 1;498    const secondExecTestVal = 2;499    await helper.testUtils.setTestValue(alice, initTestVal);500501    const currentBlockNumber = await helper.chain.getLatestBlockNumber();502    const blocksBeforeExecution = 8;503    const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution;504505    const period = 5;506507    const periodic = {508      period,509      repetitions: 2,510    };511512    // Fill `numFilledBlocks` blocks beginning from the block in which the second execution should occur513    const txs = [];514    for (let offset = 0; offset < numFilledBlocks; offset ++) {515      for (let i = 0; i < maxScheduledPerBlock; i++) {516517        const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);518519        const when = firstExecutionBlockNumber + period + offset;520        const mandatoryArgs = [when, null, null, scheduledTx];521        const scheduleArgs = scheduleKind == 'named'522          ? [fillIds[i + offset * maxScheduledPerBlock], ...mandatoryArgs]523          : mandatoryArgs;524525        const tx = helper.constructApiCall(`api.tx.scheduler.${fillScheduleFn}`, scheduleArgs);526527        txs.push(tx);528      }529    }530    await helper.executeExtrinsic(alice, 'api.tx.testUtils.batchAll', [txs], true);531532    await helper.scheduler.scheduleAt<DevUniqueHelper>(firstExecutionBlockNumber, {533      scheduledId: periodicId,534      periodic,535    }).testUtils.incTestValue(alice);536537    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber);538    expect(await helper.testUtils.testValue()).to.be.equal(firstExecTestVal);539540    await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + period + numFilledBlocks);541542    // The periodic operation should be postponed by `numFilledBlocks`543    for (let i = 0; i < numFilledBlocks; i++) {544      expect(await helper.testUtils.testValue(firstExecutionBlockNumber + period + i)).to.be.equal(firstExecTestVal);545    }546547    // After the `numFilledBlocks` the periodic operation will eventually be executed548    expect(await helper.testUtils.testValue()).to.be.equal(secondExecTestVal);549  });550551  itSched('scheduled operations does not change nonce', async (scheduleKind, {helper}) => {552    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;553    const blocksBeforeExecution = 4;554555    await helper.scheduler556      .scheduleAfter<DevUniqueHelper>(blocksBeforeExecution, {scheduledId})557      .balance.transferToSubstrate(alice, bob.address, 1n);558    const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;559560    const initNonce = await helper.chain.getNonce(alice.address);561562    await helper.wait.forParachainBlockNumber(executionBlock);563564    const finalNonce = await helper.chain.getNonce(alice.address);565566    expect(initNonce).to.be.equal(finalNonce);567  });568});569570describe('Negative Test: Scheduling', () => {571  let alice: IKeyringPair;572  let bob: IKeyringPair;573574  before(async function() {575    await usingPlaygrounds(async (helper, privateKey) => {576      requirePalletsOrSkip(this, helper, [Pallets.Scheduler]);577578      const donor = await privateKey({url: import.meta.url});579      [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);580581      await helper.testUtils.enable();582    });583  });584585  itSub("Can't overwrite a scheduled ID", async ({helper}) => {586    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});587    const token = await collection.mintToken(alice);588589    const scheduledId = helper.arrange.makeScheduledId();590    const waitForBlocks = 4;591592    await token.scheduleAfter(waitForBlocks, {scheduledId})593      .transfer(alice, {Substrate: bob.address});594    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;595596    const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});597    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal()))598      .to.be.rejectedWith(/scheduler\.FailedToSchedule/);599600    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);601602    await helper.wait.forParachainBlockNumber(executionBlock);603604    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);605606    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});607    expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter);608  });609610  itSub("Can't cancel an operation which is not scheduled", async ({helper}) => {611    const scheduledId = helper.arrange.makeScheduledId();612    await expect(helper.scheduler.cancelScheduled(alice, scheduledId))613      .to.be.rejectedWith(/scheduler\.NotFound/);614  });615616  itSub("Can't cancel a non-owned scheduled operation", async ({helper}) => {617    const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'});618    const token = await collection.mintToken(alice);619620    const scheduledId = helper.arrange.makeScheduledId();621    const waitForBlocks = 4;622623    await token.scheduleAfter(waitForBlocks, {scheduledId})624      .transfer(alice, {Substrate: bob.address});625    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;626627    await expect(helper.scheduler.cancelScheduled(bob, scheduledId))628      .to.be.rejectedWith(/BadOrigin/);629630    await helper.wait.forParachainBlockNumber(executionBlock);631632    expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address});633  });634635  itSched("Regular user can't set prioritized scheduled operation", async (scheduleKind, {helper}) => {636    const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;637    const waitForBlocks = 4;638639    const amount = 42n * helper.balance.getOneTokenNominal();640641    const balanceBefore = await helper.balance.getSubstrate(bob.address);642643    const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, priority: 42});644645    await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount))646      .to.be.rejectedWith(/BadOrigin/);647648    const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;649650    await helper.wait.forParachainBlockNumber(executionBlock);651652    const balanceAfter = await helper.balance.getSubstrate(bob.address);653654    expect(balanceAfter).to.be.equal(balanceBefore);655  });656657  itSub("Regular user can't change scheduled operation's priority", async ({helper}) => {658    const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'});659    const token = await collection.mintToken(bob);660661    const scheduledId = helper.arrange.makeScheduledId();662    const waitForBlocks = 4;663664    await token.scheduleAfter(waitForBlocks, {scheduledId})665      .transfer(bob, {Substrate: alice.address});666667    const priority = 112;668    await expect(helper.scheduler.changePriority(alice, scheduledId, priority))669      .to.be.rejectedWith(/BadOrigin/);670671    const priorityChanged = await helper.wait.event(672      waitForBlocks,673      'scheduler',674      'PriorityChanged',675    );676677    expect(priorityChanged === null).to.be.true;678  });679});680681// Implementation of the functionality tested here was postponed/shelved682describe.skip('Sponsoring scheduling', () => {683  // let alice: IKeyringPair;684  // let bob: IKeyringPair;685686  // before(async() => {687  //   await usingApi(async (_, privateKey) => {688  //     alice = privateKey('//Alice');689  //     bob = privateKey('//Bob');690  //   });691  // });692693  it('Can sponsor scheduling a transaction', async () => {694    // const collectionId = await createCollectionExpectSuccess();695    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);696    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');697698    // await usingApi(async api => {699    //   const scheduledId = await makeScheduledId();700    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);701702    //   const bobBalanceBefore = await getFreeBalance(bob);703    //   const waitForBlocks = 4;704    //   // no need to wait to check, fees must be deducted on scheduling, immediately705    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId);706    //   const bobBalanceAfter = await getFreeBalance(bob);707    //   // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;708    //   expect(bobBalanceAfter < bobBalanceBefore).to.be.true;709    //   // wait for sequentiality matters710    //   await waitNewBlocks(waitForBlocks - 1);711    // });712  });713714  it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {715    // await usingApi(async (api, privateKey) => {716    //   // Find an empty, unused account717    //   const zeroBalance = await findUnusedAddress(api, privateKey);718719    //   const collectionId = await createCollectionExpectSuccess();720721    //   // Add zeroBalance address to allow list722    //   await enablePublicMintingExpectSuccess(alice, collectionId);723    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);724725    //   // Grace zeroBalance with money, enough to cover future transactions726    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);727    //   await submitTransactionAsync(alice, balanceTx);728729    //   // Mint a fresh NFT730    //   const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');731    //   const scheduledId = await makeScheduledId();732733    //   // Schedule transfer of the NFT a few blocks ahead734    //   const waitForBlocks = 5;735    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);736737    //   // Get rid of the account's funds before the scheduled transaction takes place738    //   const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);739    //   const events = await submitTransactionAsync(zeroBalance, balanceTx2);740    //   expect(getGenericResult(events).success).to.be.true;741    //   /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?742    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);743    //   const events = await submitTransactionAsync(alice, sudoTx);744    //   expect(getGenericResult(events).success).to.be.true;*/745746    //   // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions747    //   await waitNewBlocks(waitForBlocks - 3);748749    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));750    // });751  });752753  it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {754    // const collectionId = await createCollectionExpectSuccess();755756    // await usingApi(async (api, privateKey) => {757    //   const zeroBalance = await findUnusedAddress(api, privateKey);758    //   const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);759    //   await submitTransactionAsync(alice, balanceTx);760761    //   await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);762    //   await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);763764    //   const scheduledId = await makeScheduledId();765    //   const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);766767    //   const waitForBlocks = 5;768    //   await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId);769770    //   const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);771    //   const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);772    //   const events = await submitTransactionAsync(alice, sudoTx);773    //   expect(getGenericResult(events).success).to.be.true;774775    //   // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions776    //   await waitNewBlocks(waitForBlocks - 3);777778    //   expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));779    // });780  });781782  it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {783    // const collectionId = await createCollectionExpectSuccess();784    // await setCollectionSponsorExpectSuccess(collectionId, bob.address);785    // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');786787    // await usingApi(async (api, privateKey) => {788    //   const zeroBalance = await findUnusedAddress(api, privateKey);789790    //   await enablePublicMintingExpectSuccess(alice, collectionId);791    //   await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);792793    //   const bobBalanceBefore = await getFreeBalance(bob);794795    //   const createData = {nft: {const_data: [], variable_data: []}};796    //   const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);797    //   const scheduledId = await makeScheduledId();798799    //   /*const badTransaction = async function () {800    //     await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);801    //   };802    //   await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/803804    //   await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/);805806    //   expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);807    // });808  });809});
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -9,7 +9,7 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {EventRecord} from '@polkadot/types/interfaces';
 import {ICrossAccountId, IPovInfo, TSigner} from './types';
-import {FrameSystemEventRecord} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';
 import {VoidFn} from '@polkadot/api/types';
 import {Pallets} from '..';
 import {spawnSync} from 'child_process';
@@ -59,6 +59,130 @@
   }
 }
 
+export interface IEventHelper {
+  section(): string;
+
+  method(): string;
+
+  bindEventRecord(e: FrameSystemEventRecord): void;
+
+  raw(): FrameSystemEventRecord;
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventHelper(section: string, method: string) {
+  return class implements IEventHelper {
+    eventRecord: FrameSystemEventRecord | null;
+    _section: string;
+    _method: string;
+
+    constructor() {
+      this.eventRecord = null;
+      this._section = section;
+      this._method = method;
+    }
+
+    section(): string {
+      return this._section;
+    }
+
+    method(): string {
+      return this._method;
+    }
+
+    bindEventRecord(e: FrameSystemEventRecord) {
+      this.eventRecord = e;
+    }
+
+    raw() {
+      return this.eventRecord!;
+    }
+
+    eventJsonData<T = any>(index: number) {
+      return this.raw().event.data[index].toJSON() as T;
+    }
+
+    eventData<T>(index: number) {
+      return this.raw().event.data[index] as T;
+    }
+  };
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventSection(section: string) {
+  return class Section {
+    static section = section;
+
+    static Method(name: string) {
+      return EventHelper(Section.section, name);
+    }
+  };
+}
+
+export class Event {
+  static Democracy = class extends EventSection('democracy') {
+    static Started = class extends this.Method('Started') {
+      referendumIndex() {
+        return this.eventJsonData<number>(0);
+      }
+
+      threshold() {
+        return this.eventJsonData(1);
+      }
+    };
+
+    static Voted = class extends this.Method('Voted') {
+      voter() {
+        return this.eventJsonData(0);
+      }
+
+      referendumIndex() {
+        return this.eventJsonData<number>(1);
+      }
+
+      vote() {
+        return this.eventJsonData(2);
+      }
+    };
+
+    static Passed = class extends this.Method('Passed') {
+      referendumIndex() {
+        return this.eventJsonData<number>(0);
+      }
+    };
+  };
+
+  static Scheduler = class extends EventSection('scheduler') {
+    static PriorityChanged = class extends this.Method('PriorityChanged') {
+      task() {
+        return this.eventJsonData(0);
+      }
+
+      priority() {
+        return this.eventJsonData(1);
+      }
+    };
+  };
+
+  static XcmpQueue = class extends EventSection('xcmpQueue') {
+    static XcmpMessageSent = class extends this.Method('XcmpMessageSent') {
+      messageHash() {
+        return this.eventJsonData(0);
+      }
+    };
+
+    static Fail = class extends this.Method('Fail') {
+      messageHash() {
+        return this.eventJsonData(0);
+      }
+
+      outcome() {
+        return this.eventData<XcmV2TraitsError>(1);
+      }
+    };
+  };
+}
+
 export class DevUniqueHelper extends UniqueHelper {
   /**
    * Arrange methods for tests
@@ -634,12 +758,12 @@
     console.log('\t* Fast track proposal through technical committee.......DONE');
     // <<< Fast track proposal through technical committee <<<
 
-    const refIndexField = 0;
-    const referendumIndex = await this.helper.wait.eventData<number>(3, 'democracy', 'Started', refIndexField);
+    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+    const referendumIndex = democracyStarted.referendumIndex();
 
     // >>> Referendum voting >>>
     console.log(`\t* Referendum #${referendumIndex} voting.......`);
-    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex!, {
+    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {
       balance: 10_000_000_000_000_000_000n,
       vote: {aye: true, conviction: 1},
     });
@@ -647,7 +771,10 @@
     // <<< Referendum voting <<<
 
     // Wait the proposal to pass
-    await this.helper.wait.event(3, 'democracy', 'Passed');
+    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => {
+      return event.referendumIndex() == referendumIndex;
+    });
+
     await this.helper.wait.newBlocks(1);
 
     console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
@@ -794,13 +921,18 @@
     return promise;
   }
 
-  event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
+  event<T extends IEventHelper>(
+    maxBlocksToWait: number,
+    eventHelperType: new () => T,
+    filter: (_: T) => boolean = () => { return true; },
+  ) {
     // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<EventRecord | null>(async (resolve) => {
+    const promise = new Promise<T | null>(async (resolve) => {
       const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
+        const eventHelper = new eventHelperType();
         const blockNumber = header.number.toHuman();
         const blockHash = header.hash;
-        const eventIdStr = `${eventSection}.${eventMethod}`;
+        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;
         const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
 
         this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
@@ -809,16 +941,24 @@
         const eventRecords = (await apiAt.query.system.events()) as any;
 
         const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {
-          return r.event.section == eventSection && r.event.method == eventMethod;
+          if (
+            r.event.section == eventHelper.section()
+            && r.event.method == eventHelper.method()
+          ) {
+            eventHelper.bindEventRecord(r);
+            return filter(eventHelper);
+          } else {
+            return false;
+          }
         });
 
         if (neededEvent) {
           unsubscribe();
-          resolve(neededEvent);
+          resolve(eventHelper);
         } else if (maxBlocksToWait > 0) {
           maxBlocksToWait--;
         } else {
-          this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);
+          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);
           unsubscribe();
           resolve(null);
         }
@@ -827,17 +967,18 @@
     return promise;
   }
 
-  async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {
-    const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);
-
-    if (eventRecord == null) {
-      return null;
+  async expectEvent<T extends IEventHelper>(
+    maxBlocksToWait: number,
+    eventHelperType: new () => T,
+    filter: (e: T) => boolean = () => { return true; },
+  ) {
+    const e = await this.event(maxBlocksToWait, eventHelperType, filter);
+    if (e == null) {
+      const eventHelper = new eventHelperType();
+      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);
+    } else {
+      return e;
     }
-
-    const event = eventRecord!.event;
-    const data = event.data[fieldIndex] as EventT;
-
-    return data;
   }
 }
 
modifiedtests/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);
modifiedtests/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);