git.delta.rocks / unique-network / refs/commits / bcc1c229c277

difftreelog

fix add/extend xcm tests for moonbeam, extend untrusted reserve location

Daniel Shiposha2023-04-13parent: #e02f22f.patch.diff
in: master

4 files changed

modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
before · tests/src/util/playgrounds/unique.dev.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, IPovInfo, TSigner} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';1617export class SilentLogger {18  log(_msg: any, _level: any): void { }19  level = {20    ERROR: 'ERROR' as const,21    WARNING: 'WARNING' as const,22    INFO: 'INFO' as const,23  };24}2526export class SilentConsole {27  // TODO: Remove, this is temporary: Filter unneeded API output28  // (Jaco promised it will be removed in the next version)29  consoleErr: any;30  consoleLog: any;31  consoleWarn: any;3233  constructor() {34    this.consoleErr = console.error;35    this.consoleLog = console.log;36    this.consoleWarn = console.warn;37  }3839  enable() {40    const outFn = (printer: any) => (...args: any[]) => {41      for (const arg of args) {42        if (typeof arg !== 'string')43          continue;44        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')45          return;46      }47      printer(...args);48    };4950    console.error = outFn(this.consoleErr.bind(console));51    console.log = outFn(this.consoleLog.bind(console));52    console.warn = outFn(this.consoleWarn.bind(console));53  }5455  disable() {56    console.error = this.consoleErr;57    console.log = this.consoleLog;58    console.warn = this.consoleWarn;59  }60}6162export class DevUniqueHelper extends UniqueHelper {63  /**64   * Arrange methods for tests65   */66  arrange: ArrangeGroup;67  wait: WaitGroup;68  admin: AdminGroup;69  session: SessionGroup;70  testUtils: TestUtilGroup;7172  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {73    options.helperBase = options.helperBase ?? DevUniqueHelper;7475    super(logger, options);76    this.arrange = new ArrangeGroup(this);77    this.wait = new WaitGroup(this);78    this.admin = new AdminGroup(this);79    this.testUtils = new TestUtilGroup(this);80    this.session = new SessionGroup(this);81  }8283  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {84    const wsProvider = new WsProvider(wsEndpoint);85    this.api = new ApiPromise({86      provider: wsProvider,87      signedExtensions: {88        ContractHelpers: {89          extrinsic: {},90          payload: {},91        },92        CheckMaintenance: {93          extrinsic: {},94          payload: {},95        },96        DisableIdentityCalls: {97          extrinsic: {},98          payload: {},99        },100        FakeTransactionFinalizer: {101          extrinsic: {},102          payload: {},103        },104      },105      rpc: {106        unique: defs.unique.rpc,107        appPromotion: defs.appPromotion.rpc,108        povinfo: defs.povinfo.rpc,109        eth: {110          feeHistory: {111            description: 'Dummy',112            params: [],113            type: 'u8',114          },115          maxPriorityFeePerGas: {116            description: 'Dummy',117            params: [],118            type: 'u8',119          },120        },121      },122    });123    await this.api.isReadyOrError;124    this.network = await UniqueHelper.detectNetwork(this.api);125    this.wsEndpoint = wsEndpoint;126  }127}128129export class DevRelayHelper extends RelayHelper {130  wait: WaitGroup;131132  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {133    options.helperBase = options.helperBase ?? DevRelayHelper;134135    super(logger, options);136    this.wait = new WaitGroup(this);137  }138}139140export class DevWestmintHelper extends WestmintHelper {141  wait: WaitGroup;142143  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {144    options.helperBase = options.helperBase ?? DevWestmintHelper;145146    super(logger, options);147    this.wait = new WaitGroup(this);148  }149}150151export class DevStatemineHelper extends DevWestmintHelper {}152153export class DevStatemintHelper extends DevWestmintHelper {}154155export class DevMoonbeamHelper extends MoonbeamHelper {156  account: MoonbeamAccountGroup;157  wait: WaitGroup;158159  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {160    options.helperBase = options.helperBase ?? DevMoonbeamHelper;161    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';162163    super(logger, options);164    this.account = new MoonbeamAccountGroup(this);165    this.wait = new WaitGroup(this);166  }167}168169export class DevMoonriverHelper extends DevMoonbeamHelper {170  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {171    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';172    super(logger, options);173  }174}175176export class DevAstarHelper extends AstarHelper {177  wait: WaitGroup;178179  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {180    options.helperBase = options.helperBase ?? DevAstarHelper;181182    super(logger, options);183    this.wait = new WaitGroup(this);184  }185}186187export class DevShidenHelper extends AstarHelper {188  wait: WaitGroup;189190  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {191    options.helperBase = options.helperBase ?? DevShidenHelper;192193    super(logger, options);194    this.wait = new WaitGroup(this);195  }196}197198export class DevAcalaHelper extends AcalaHelper {199  wait: WaitGroup;200201  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {202    options.helperBase = options.helperBase ?? DevAcalaHelper;203204    super(logger, options);205    this.wait = new WaitGroup(this);206  }207}208209export class DevKaruraHelper extends DevAcalaHelper {}210211export class ArrangeGroup {212  helper: DevUniqueHelper;213214  scheduledIdSlider = 0;215216  constructor(helper: DevUniqueHelper) {217    this.helper = helper;218  }219220  /**221   * Generates accounts with the specified UNQ token balance222   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.223   * @param donor donor account for balances224   * @returns array of newly created accounts225   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);226   */227  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {228    let nonce = await this.helper.chain.getNonce(donor.address);229    const wait = new WaitGroup(this.helper);230    const ss58Format = this.helper.chain.getChainProperties().ss58Format;231    const tokenNominal = this.helper.balance.getOneTokenNominal();232    const transactions = [];233    const accounts: IKeyringPair[] = [];234    for (const balance of balances) {235      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);236      accounts.push(recipient);237      if (balance !== 0n) {238        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);239        transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));240        nonce++;241      }242    }243244    await Promise.all(transactions).catch(_e => {});245246    //#region TODO remove this region, when nonce problem will be solved247    const checkBalances = async () => {248      let isSuccess = true;249      for (let i = 0; i < balances.length; i++) {250        const balance = await this.helper.balance.getSubstrate(accounts[i].address);251        if (balance !== balances[i] * tokenNominal) {252          isSuccess = false;253          break;254        }255      }256      return isSuccess;257    };258259    let accountsCreated = false;260    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;261    // checkBalances retry up to 5-50 blocks262    for (let index = 0; index < maxBlocksChecked; index++) {263      accountsCreated = await checkBalances();264      if(accountsCreated) break;265      await wait.newBlocks(1);266    }267268    if (!accountsCreated) throw Error('Accounts generation failed');269    //#endregion270271    return accounts;272  };273274  // TODO combine this method and createAccounts into one275  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {276    const createAsManyAsCan = async () => {277      let transactions: any = [];278      const accounts: IKeyringPair[] = [];279      let nonce = await this.helper.chain.getNonce(donor.address);280      const tokenNominal = this.helper.balance.getOneTokenNominal();281      const ss58Format = this.helper.chain.getChainProperties().ss58Format;282      for (let i = 0; i < accountsToCreate; i++) {283        if (i === 500) { // if there are too many accounts to create284          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled285          transactions = []; //286          nonce = await this.helper.chain.getNonce(donor.address); // update nonce287        }288        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);289        accounts.push(recipient);290        if (withBalance !== 0n) {291          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);292          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));293          nonce++;294        }295      }296297      const fullfilledAccounts = [];298      await Promise.allSettled(transactions);299      for (const account of accounts) {300        const accountBalance = await this.helper.balance.getSubstrate(account.address);301        if (accountBalance === withBalance * tokenNominal) {302          fullfilledAccounts.push(account);303        }304      }305      return fullfilledAccounts;306    };307308309    const crowd: IKeyringPair[] = [];310    // do up to 5 retries311    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {312      const asManyAsCan = await createAsManyAsCan();313      crowd.push(...asManyAsCan);314      accountsToCreate -= asManyAsCan.length;315    }316317    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);318319    return crowd;320  };321322  isDevNode = async () => {323    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();324    if (blockNumber == 0) {325      await this.helper.wait.newBlocks(1);326      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();327    }328    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);329    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);330    const findCreationDate = (block: any) => {331      const humanBlock = block.toHuman();332      let date;333      humanBlock.block.extrinsics.forEach((ext: any) => {334        if(ext.method.section === 'timestamp') {335          date = Number(ext.method.args.now.replaceAll(',', ''));336        }337      });338      return date;339    };340    const block1date = await findCreationDate(block1);341    const block2date = await findCreationDate(block2);342    if(block2date! - block1date! < 9000) return true;343  };344345  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {346    const address = payer.Substrate ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum!);347    let balance = await this.helper.balance.getSubstrate(address);348349    await promise();350351    balance -= await this.helper.balance.getSubstrate(address);352353    return balance;354  }355356  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {357    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);358359    const kvJson: {[key: string]: string} = {};360361    for (const kv of rawPovInfo.keyValues) {362      kvJson[kv.key.toHex()] = kv.value.toHex();363    }364365    const kvStr = JSON.stringify(kvJson);366367    const chainql = spawnSync(368      'chainql',369      [370        `--tla-code=data=${kvStr}`,371        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,372      ],373    );374375    if (!chainql.stdout) {376      throw Error('unable to get an output from the `chainql`');377    }378379    return {380      proofSize: rawPovInfo.proofSize.toNumber(),381      compactProofSize: rawPovInfo.compactProofSize.toNumber(),382      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),383      results: rawPovInfo.results,384      kv: JSON.parse(chainql.stdout.toString()),385    };386  }387388  calculatePalletAddress(palletId: any) {389    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));390    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);391  }392393  makeScheduledIds(num: number): string[] {394    function makeId(slider: number) {395      const scheduledIdSize = 64;396      const hexId = slider.toString(16);397      const prefixSize = scheduledIdSize - hexId.length;398399      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;400401      return scheduledId;402    }403404    const ids = [];405    for (let i = 0; i < num; i++) {406      ids.push(makeId(this.scheduledIdSlider));407      this.scheduledIdSlider += 1;408    }409410    return ids;411  }412413  makeScheduledId(): string {414    return (this.makeScheduledIds(1))[0];415  }416417  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {418    const capture = new EventCapture(this.helper, eventSection, eventMethod);419    await capture.startCapture();420421    return capture;422  }423424  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {425    return {426      V2: [427        {428          WithdrawAsset: [429            {430              id,431              fun: {432                Fungible: amount,433              },434            },435          ],436        },437        {438          BuyExecution: {439            fees: {440              id,441              fun: {442                Fungible: amount,443              },444            },445            weightLimit: 'Unlimited',446          },447        },448        {449          DepositAsset: {450            assets: {451              Wild: 'All',452            },453            maxAssets: 1,454            beneficiary: {455              parents: 0,456              interior: {457                X1: {458                  AccountId32: {459                    network: 'Any',460                    id: beneficiary,461                  },462                },463              },464            },465          },466        },467      ],468    };469  }470471  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {472    return {473      V2: [474        {475          ReserveAssetDeposited: [476            {477              id,478              fun: {479                Fungible: amount,480              },481            },482          ],483        },484        {485          BuyExecution: {486            fees: {487              id,488              fun: {489                Fungible: amount,490              },491            },492            weightLimit: 'Unlimited',493          },494        },495        {496          DepositAsset: {497            assets: {498              Wild: 'All',499            },500            maxAssets: 1,501            beneficiary: {502              parents: 0,503              interior: {504                X1: {505                  AccountId32: {506                    network: 'Any',507                    id: beneficiary,508                  },509                },510              },511            },512          },513        },514      ],515    };516  }517}518519class MoonbeamAccountGroup {520  helper: MoonbeamHelper;521522  keyring: Keyring;523  _alithAccount: IKeyringPair;524  _baltatharAccount: IKeyringPair;525  _dorothyAccount: IKeyringPair;526527  constructor(helper: MoonbeamHelper) {528    this.helper = helper;529530    this.keyring = new Keyring({type: 'ethereum'});531    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';532    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';533    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';534535    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');536    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');537    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');538  }539540  alithAccount() {541    return this._alithAccount;542  }543544  baltatharAccount() {545    return this._baltatharAccount;546  }547548  dorothyAccount() {549    return this._dorothyAccount;550  }551552  create() {553    return this.keyring.addFromUri(mnemonicGenerate());554  }555}556557class WaitGroup {558  helper: ChainHelperBase;559560  constructor(helper: ChainHelperBase) {561    this.helper = helper;562  }563564  sleep(milliseconds: number) {565    return new Promise((resolve) => setTimeout(resolve, milliseconds));566  }567568  private async waitWithTimeout(promise: Promise<any>, timeout: number) {569    let isBlock = false;570    promise.then(() => isBlock = true).catch(() => isBlock = true);571    let totalTime = 0;572    const step = 100;573    while(!isBlock) {574      await this.sleep(step);575      totalTime += step;576      if(totalTime >= timeout) throw Error('Blocks production failed');577    }578    return promise;579  }580581  /**582   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.583   * @param promise async operation to race against the timeout584   * @param timeoutMS time after which to time out585   * @param timeoutError error message to throw586   * @returns promise of the same type the operation had587   */588  withTimeout<T>(589    promise: Promise<T>,590    timeoutMS = 30000,591    timeoutError = 'The operation has timed out!',592  ): Promise<T> {593    const timeout = new Promise<never>((_, reject) => {594      setTimeout(() => {595        reject(new Error(timeoutError));596      }, timeoutMS);597    });598599    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});600  }601602  /**603   * Wait for specified number of blocks604   * @param blocksCount number of blocks to wait605   * @returns606   */607  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {608    timeout = timeout ?? blocksCount * 60_000;609    // eslint-disable-next-line no-async-promise-executor610    const promise = new Promise<void>(async (resolve) => {611      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {612        if (blocksCount > 0) {613          blocksCount--;614        } else {615          unsubscribe();616          resolve();617        }618      });619    });620    await this.waitWithTimeout(promise, timeout);621    return promise;622  }623624  /**625   * Wait for the specified number of sessions to pass.626   * Only applicable if the Session pallet is turned on.627   * @param sessionCount number of sessions to wait628   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks629   * @returns630   */631  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {632    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`633      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');634635    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;636    let currentSessionIndex = -1;637638    while (currentSessionIndex < expectedSessionIndex) {639      // eslint-disable-next-line no-async-promise-executor640      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {641        await this.newBlocks(1);642        const res = await (this.helper as DevUniqueHelper).session.getIndex();643        resolve(res);644      }), blockTimeout, 'The chain has stopped producing blocks!');645    }646  }647648  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {649    timeout = timeout ?? 30 * 60 * 1000;650    // eslint-disable-next-line no-async-promise-executor651    const promise = new Promise<void>(async (resolve) => {652      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {653        if (data.number.toNumber() >= blockNumber) {654          unsubscribe();655          resolve();656        }657      });658    });659    await this.waitWithTimeout(promise, timeout);660    return promise;661  }662663  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {664    timeout = timeout ?? 30 * 60 * 1000;665    // eslint-disable-next-line no-async-promise-executor666    const promise = new Promise<void>(async (resolve) => {667      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {668        if (data.value.relayParentNumber.toNumber() >= blockNumber) {669          // @ts-ignore670          unsubscribe();671          resolve();672        }673      });674    });675    await this.waitWithTimeout(promise, timeout);676    return promise;677  }678679  noScheduledTasks() {680    const api = this.helper.getApi();681682    // eslint-disable-next-line no-async-promise-executor683    const promise = new Promise<void>(async resolve => {684      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {685        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();686687        if(areThereScheduledTasks.length == 0) {688          unsubscribe();689          resolve();690        }691      });692    });693694    return promise;695  }696697  event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {698    // eslint-disable-next-line no-async-promise-executor699    const promise = new Promise<EventRecord | null>(async (resolve) => {700      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {701        const blockNumber = header.number.toHuman();702        const blockHash = header.hash;703        const eventIdStr = `${eventSection}.${eventMethod}`;704        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;705706        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);707708        const apiAt = await this.helper.getApi().at(blockHash);709        const eventRecords = (await apiAt.query.system.events()) as any;710711        const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {712          return r.event.section == eventSection && r.event.method == eventMethod;713        });714715        if (neededEvent) {716          unsubscribe();717          resolve(neededEvent);718        } else if (maxBlocksToWait > 0) {719          maxBlocksToWait--;720        } else {721          this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);722          unsubscribe();723          resolve(null);724        }725      });726    });727    return promise;728  }729730  async eventOutcome<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string) {731    const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);732733    if (eventRecord == null) {734      return null;735    }736737    const event = eventRecord!.event;738    const outcome = event.data[1] as EventT;739740    return outcome;741  }742}743744class SessionGroup {745  helper: ChainHelperBase;746747  constructor(helper: ChainHelperBase) {748    this.helper = helper;749  }750751  //todo:collator documentation752  async getIndex(): Promise<number> {753    return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();754  }755756  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {757    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);758  }759760  setOwnKeys(signer: TSigner, key: string) {761    return this.helper.executeExtrinsic(762      signer,763      'api.tx.session.setKeys',764      [key, '0x0'],765      true,766    );767  }768769  setOwnKeysFromAddress(signer: TSigner) {770    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));771  }772}773774class TestUtilGroup {775  helper: DevUniqueHelper;776777  constructor(helper: DevUniqueHelper) {778    this.helper = helper;779  }780781  async enable() {782    if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {783      return;784    }785786    const signer = this.helper.util.fromSeed('//Alice');787    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);788  }789790  async setTestValue(signer: TSigner, testVal: number) {791    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);792  }793794  async incTestValue(signer: TSigner) {795    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);796  }797798  async setTestValueAndRollback(signer: TSigner, testVal: number) {799    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);800  }801802  async testValue(blockIdx?: number) {803    const api = blockIdx804      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))805      : this.helper.getApi();806807    return (await api.query.testUtils.testValue()).toJSON();808  }809810  async justTakeFee(signer: TSigner) {811    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);812  }813814  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {815    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);816  }817}818819class EventCapture {820  helper: DevUniqueHelper;821  eventSection: string;822  eventMethod: string;823  events: EventRecord[] = [];824  unsubscribe: VoidFn | null = null;825826  constructor(827    helper: DevUniqueHelper,828    eventSection: string,829    eventMethod: string,830  ) {831    this.helper = helper;832    this.eventSection = eventSection;833    this.eventMethod = eventMethod;834  }835836  async startCapture() {837    this.stopCapture();838    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {839      const newEvents = eventRecords.filter(r => {840        return r.event.section == this.eventSection && r.event.method == this.eventMethod;841      });842843      this.events.push(...newEvents);844    })) as any;845  }846847  stopCapture() {848    if (this.unsubscribe !== null) {849      this.unsubscribe();850    }851  }852853  extractCapturedEvents() {854    return this.events;855  }856}857858class AdminGroup {859  helper: UniqueHelper;860861  constructor(helper: UniqueHelper) {862    this.helper = helper;863  }864865  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {866    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);867    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {868      return {869        staker: e.event.data[0].toString(),870        stake: e.event.data[1].toBigInt(),871        payout: e.event.data[2].toBigInt(),872      };873    });874  }875}
after · tests/src/util/playgrounds/unique.dev.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, IPovInfo, TSigner} from './types';12import {FrameSystemEventRecord} from '@polkadot/types/lookup';13import {VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';1617export class SilentLogger {18  log(_msg: any, _level: any): void { }19  level = {20    ERROR: 'ERROR' as const,21    WARNING: 'WARNING' as const,22    INFO: 'INFO' as const,23  };24}2526export class SilentConsole {27  // TODO: Remove, this is temporary: Filter unneeded API output28  // (Jaco promised it will be removed in the next version)29  consoleErr: any;30  consoleLog: any;31  consoleWarn: any;3233  constructor() {34    this.consoleErr = console.error;35    this.consoleLog = console.log;36    this.consoleWarn = console.warn;37  }3839  enable() {40    const outFn = (printer: any) => (...args: any[]) => {41      for (const arg of args) {42        if (typeof arg !== 'string')43          continue;44        if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure')45          return;46      }47      printer(...args);48    };4950    console.error = outFn(this.consoleErr.bind(console));51    console.log = outFn(this.consoleLog.bind(console));52    console.warn = outFn(this.consoleWarn.bind(console));53  }5455  disable() {56    console.error = this.consoleErr;57    console.log = this.consoleLog;58    console.warn = this.consoleWarn;59  }60}6162export class DevUniqueHelper extends UniqueHelper {63  /**64   * Arrange methods for tests65   */66  arrange: ArrangeGroup;67  wait: WaitGroup;68  admin: AdminGroup;69  session: SessionGroup;70  testUtils: TestUtilGroup;7172  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {73    options.helperBase = options.helperBase ?? DevUniqueHelper;7475    super(logger, options);76    this.arrange = new ArrangeGroup(this);77    this.wait = new WaitGroup(this);78    this.admin = new AdminGroup(this);79    this.testUtils = new TestUtilGroup(this);80    this.session = new SessionGroup(this);81  }8283  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {84    const wsProvider = new WsProvider(wsEndpoint);85    this.api = new ApiPromise({86      provider: wsProvider,87      signedExtensions: {88        ContractHelpers: {89          extrinsic: {},90          payload: {},91        },92        CheckMaintenance: {93          extrinsic: {},94          payload: {},95        },96        DisableIdentityCalls: {97          extrinsic: {},98          payload: {},99        },100        FakeTransactionFinalizer: {101          extrinsic: {},102          payload: {},103        },104      },105      rpc: {106        unique: defs.unique.rpc,107        appPromotion: defs.appPromotion.rpc,108        povinfo: defs.povinfo.rpc,109        eth: {110          feeHistory: {111            description: 'Dummy',112            params: [],113            type: 'u8',114          },115          maxPriorityFeePerGas: {116            description: 'Dummy',117            params: [],118            type: 'u8',119          },120        },121      },122    });123    await this.api.isReadyOrError;124    this.network = await UniqueHelper.detectNetwork(this.api);125    this.wsEndpoint = wsEndpoint;126  }127}128129export class DevRelayHelper extends RelayHelper {130  wait: WaitGroup;131132  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {133    options.helperBase = options.helperBase ?? DevRelayHelper;134135    super(logger, options);136    this.wait = new WaitGroup(this);137  }138}139140export class DevWestmintHelper extends WestmintHelper {141  wait: WaitGroup;142143  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {144    options.helperBase = options.helperBase ?? DevWestmintHelper;145146    super(logger, options);147    this.wait = new WaitGroup(this);148  }149}150151export class DevStatemineHelper extends DevWestmintHelper {}152153export class DevStatemintHelper extends DevWestmintHelper {}154155export class DevMoonbeamHelper extends MoonbeamHelper {156  account: MoonbeamAccountGroup;157  wait: WaitGroup;158  fastDemocracy: MoonbeamFastDemocracyGroup;159160  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {161    options.helperBase = options.helperBase ?? DevMoonbeamHelper;162    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';163164    super(logger, options);165    this.account = new MoonbeamAccountGroup(this);166    this.wait = new WaitGroup(this);167    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);168  }169}170171export class DevMoonriverHelper extends DevMoonbeamHelper {172  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {173    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';174    super(logger, options);175  }176}177178export class DevAstarHelper extends AstarHelper {179  wait: WaitGroup;180181  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {182    options.helperBase = options.helperBase ?? DevAstarHelper;183184    super(logger, options);185    this.wait = new WaitGroup(this);186  }187}188189export class DevShidenHelper extends AstarHelper {190  wait: WaitGroup;191192  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {193    options.helperBase = options.helperBase ?? DevShidenHelper;194195    super(logger, options);196    this.wait = new WaitGroup(this);197  }198}199200export class DevAcalaHelper extends AcalaHelper {201  wait: WaitGroup;202203  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {204    options.helperBase = options.helperBase ?? DevAcalaHelper;205206    super(logger, options);207    this.wait = new WaitGroup(this);208  }209}210211export class DevKaruraHelper extends DevAcalaHelper {}212213export class ArrangeGroup {214  helper: DevUniqueHelper;215216  scheduledIdSlider = 0;217218  constructor(helper: DevUniqueHelper) {219    this.helper = helper;220  }221222  /**223   * Generates accounts with the specified UNQ token balance224   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.225   * @param donor donor account for balances226   * @returns array of newly created accounts227   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);228   */229  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {230    let nonce = await this.helper.chain.getNonce(donor.address);231    const wait = new WaitGroup(this.helper);232    const ss58Format = this.helper.chain.getChainProperties().ss58Format;233    const tokenNominal = this.helper.balance.getOneTokenNominal();234    const transactions = [];235    const accounts: IKeyringPair[] = [];236    for (const balance of balances) {237      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);238      accounts.push(recipient);239      if (balance !== 0n) {240        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);241        transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));242        nonce++;243      }244    }245246    await Promise.all(transactions).catch(_e => {});247248    //#region TODO remove this region, when nonce problem will be solved249    const checkBalances = async () => {250      let isSuccess = true;251      for (let i = 0; i < balances.length; i++) {252        const balance = await this.helper.balance.getSubstrate(accounts[i].address);253        if (balance !== balances[i] * tokenNominal) {254          isSuccess = false;255          break;256        }257      }258      return isSuccess;259    };260261    let accountsCreated = false;262    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;263    // checkBalances retry up to 5-50 blocks264    for (let index = 0; index < maxBlocksChecked; index++) {265      accountsCreated = await checkBalances();266      if(accountsCreated) break;267      await wait.newBlocks(1);268    }269270    if (!accountsCreated) throw Error('Accounts generation failed');271    //#endregion272273    return accounts;274  };275276  // TODO combine this method and createAccounts into one277  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {278    const createAsManyAsCan = async () => {279      let transactions: any = [];280      const accounts: IKeyringPair[] = [];281      let nonce = await this.helper.chain.getNonce(donor.address);282      const tokenNominal = this.helper.balance.getOneTokenNominal();283      const ss58Format = this.helper.chain.getChainProperties().ss58Format;284      for (let i = 0; i < accountsToCreate; i++) {285        if (i === 500) { // if there are too many accounts to create286          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled287          transactions = []; //288          nonce = await this.helper.chain.getNonce(donor.address); // update nonce289        }290        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);291        accounts.push(recipient);292        if (withBalance !== 0n) {293          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);294          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));295          nonce++;296        }297      }298299      const fullfilledAccounts = [];300      await Promise.allSettled(transactions);301      for (const account of accounts) {302        const accountBalance = await this.helper.balance.getSubstrate(account.address);303        if (accountBalance === withBalance * tokenNominal) {304          fullfilledAccounts.push(account);305        }306      }307      return fullfilledAccounts;308    };309310311    const crowd: IKeyringPair[] = [];312    // do up to 5 retries313    for (let index = 0; index < 5 && accountsToCreate !== 0; index++) {314      const asManyAsCan = await createAsManyAsCan();315      crowd.push(...asManyAsCan);316      accountsToCreate -= asManyAsCan.length;317    }318319    if (accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);320321    return crowd;322  };323324  isDevNode = async () => {325    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();326    if (blockNumber == 0) {327      await this.helper.wait.newBlocks(1);328      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();329    }330    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);331    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);332    const findCreationDate = (block: any) => {333      const humanBlock = block.toHuman();334      let date;335      humanBlock.block.extrinsics.forEach((ext: any) => {336        if(ext.method.section === 'timestamp') {337          date = Number(ext.method.args.now.replaceAll(',', ''));338        }339      });340      return date;341    };342    const block1date = await findCreationDate(block1);343    const block2date = await findCreationDate(block2);344    if(block2date! - block1date! < 9000) return true;345  };346347  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {348    const address = payer.Substrate ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum!);349    let balance = await this.helper.balance.getSubstrate(address);350351    await promise();352353    balance -= await this.helper.balance.getSubstrate(address);354355    return balance;356  }357358  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {359    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);360361    const kvJson: {[key: string]: string} = {};362363    for (const kv of rawPovInfo.keyValues) {364      kvJson[kv.key.toHex()] = kv.value.toHex();365    }366367    const kvStr = JSON.stringify(kvJson);368369    const chainql = spawnSync(370      'chainql',371      [372        `--tla-code=data=${kvStr}`,373        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,374      ],375    );376377    if (!chainql.stdout) {378      throw Error('unable to get an output from the `chainql`');379    }380381    return {382      proofSize: rawPovInfo.proofSize.toNumber(),383      compactProofSize: rawPovInfo.compactProofSize.toNumber(),384      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),385      results: rawPovInfo.results,386      kv: JSON.parse(chainql.stdout.toString()),387    };388  }389390  calculatePalletAddress(palletId: any) {391    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));392    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);393  }394395  makeScheduledIds(num: number): string[] {396    function makeId(slider: number) {397      const scheduledIdSize = 64;398      const hexId = slider.toString(16);399      const prefixSize = scheduledIdSize - hexId.length;400401      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;402403      return scheduledId;404    }405406    const ids = [];407    for (let i = 0; i < num; i++) {408      ids.push(makeId(this.scheduledIdSlider));409      this.scheduledIdSlider += 1;410    }411412    return ids;413  }414415  makeScheduledId(): string {416    return (this.makeScheduledIds(1))[0];417  }418419  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {420    const capture = new EventCapture(this.helper, eventSection, eventMethod);421    await capture.startCapture();422423    return capture;424  }425426  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {427    return {428      V2: [429        {430          WithdrawAsset: [431            {432              id,433              fun: {434                Fungible: amount,435              },436            },437          ],438        },439        {440          BuyExecution: {441            fees: {442              id,443              fun: {444                Fungible: amount,445              },446            },447            weightLimit: 'Unlimited',448          },449        },450        {451          DepositAsset: {452            assets: {453              Wild: 'All',454            },455            maxAssets: 1,456            beneficiary: {457              parents: 0,458              interior: {459                X1: {460                  AccountId32: {461                    network: 'Any',462                    id: beneficiary,463                  },464                },465              },466            },467          },468        },469      ],470    };471  }472473  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {474    return {475      V2: [476        {477          ReserveAssetDeposited: [478            {479              id,480              fun: {481                Fungible: amount,482              },483            },484          ],485        },486        {487          BuyExecution: {488            fees: {489              id,490              fun: {491                Fungible: amount,492              },493            },494            weightLimit: 'Unlimited',495          },496        },497        {498          DepositAsset: {499            assets: {500              Wild: 'All',501            },502            maxAssets: 1,503            beneficiary: {504              parents: 0,505              interior: {506                X1: {507                  AccountId32: {508                    network: 'Any',509                    id: beneficiary,510                  },511                },512              },513            },514          },515        },516      ],517    };518  }519}520521class MoonbeamAccountGroup {522  helper: MoonbeamHelper;523524  keyring: Keyring;525  _alithAccount: IKeyringPair;526  _baltatharAccount: IKeyringPair;527  _dorothyAccount: IKeyringPair;528529  constructor(helper: MoonbeamHelper) {530    this.helper = helper;531532    this.keyring = new Keyring({type: 'ethereum'});533    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';534    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';535    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';536537    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');538    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');539    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');540  }541542  alithAccount() {543    return this._alithAccount;544  }545546  baltatharAccount() {547    return this._baltatharAccount;548  }549550  dorothyAccount() {551    return this._dorothyAccount;552  }553554  create() {555    return this.keyring.addFromUri(mnemonicGenerate());556  }557}558559class MoonbeamFastDemocracyGroup {560  helper: DevMoonbeamHelper;561562  constructor(helper: DevMoonbeamHelper) {563    this.helper = helper;564  }565566  async executeProposal(proposalDesciption: string, encodedProposal: string) {567    const proposalHash = blake2AsHex(encodedProposal);568569    const alithAccount = this.helper.account.alithAccount();570    const baltatharAccount = this.helper.account.baltatharAccount();571    const dorothyAccount = this.helper.account.dorothyAccount();572573    const councilVotingThreshold = 2;574    const technicalCommitteeThreshold = 2;575    const fastTrackVotingPeriod = 3;576    const fastTrackDelayPeriod = 0;577578    console.log(`[democracy] executing '${proposalDesciption}' proposal`);579580    // >>> Propose external motion through council >>>581    console.log('\t* Propose external motion through council.......');582    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});583    const encodedMotion = externalMotion?.method.toHex() || '';584    const motionHash = blake2AsHex(encodedMotion);585    console.log('\t* Motion hash is %s', motionHash);586587    await this.helper.collective.council.propose(588      baltatharAccount,589      councilVotingThreshold,590      externalMotion,591      externalMotion.encodedLength,592    );593594    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;595    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);596    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);597598    await this.helper.collective.council.close(599      dorothyAccount,600      motionHash,601      councilProposalIdx,602      {603        refTime: 1_000_000_000,604        proofSize: 1_000_000,605      },606      externalMotion.encodedLength,607    );608    console.log('\t* Propose external motion through council.......DONE');609    // <<< Propose external motion through council <<<610611    // >>> Fast track proposal through technical committee >>>612    console.log('\t* Fast track proposal through technical committee.......');613    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);614    const encodedFastTrack = fastTrack?.method.toHex() || '';615    const fastTrackHash = blake2AsHex(encodedFastTrack);616    console.log('\t* FastTrack hash is %s', fastTrackHash);617618    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);619620    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;621    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);622    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);623624    await this.helper.collective.techCommittee.close(625      baltatharAccount,626      fastTrackHash,627      techProposalIdx,628      {629        refTime: 1_000_000_000,630        proofSize: 1_000_000,631      },632      fastTrack.encodedLength,633    );634    console.log('\t* Fast track proposal through technical committee.......DONE');635    // <<< Fast track proposal through technical committee <<<636637    const refIndexField = 0;638    const referendumIndex = await this.helper.wait.eventData<any>(3, 'democracy', 'Started', refIndexField);639640    // >>> Referendum voting >>>641    console.log(`\t* Referendum #${referendumIndex} voting.......`);642    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {643      balance: 10_000_000_000_000_000_000n,644      vote: {aye: true, conviction: 1},645    });646    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);647    // <<< Referendum voting <<<648649    // Wait for the democracy execute650    await this.helper.wait.newBlocks(5);651652    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);653  }654}655656class WaitGroup {657  helper: ChainHelperBase;658659  constructor(helper: ChainHelperBase) {660    this.helper = helper;661  }662663  sleep(milliseconds: number) {664    return new Promise((resolve) => setTimeout(resolve, milliseconds));665  }666667  private async waitWithTimeout(promise: Promise<any>, timeout: number) {668    let isBlock = false;669    promise.then(() => isBlock = true).catch(() => isBlock = true);670    let totalTime = 0;671    const step = 100;672    while(!isBlock) {673      await this.sleep(step);674      totalTime += step;675      if(totalTime >= timeout) throw Error('Blocks production failed');676    }677    return promise;678  }679680  /**681   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.682   * @param promise async operation to race against the timeout683   * @param timeoutMS time after which to time out684   * @param timeoutError error message to throw685   * @returns promise of the same type the operation had686   */687  withTimeout<T>(688    promise: Promise<T>,689    timeoutMS = 30000,690    timeoutError = 'The operation has timed out!',691  ): Promise<T> {692    const timeout = new Promise<never>((_, reject) => {693      setTimeout(() => {694        reject(new Error(timeoutError));695      }, timeoutMS);696    });697698    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});699  }700701  /**702   * Wait for specified number of blocks703   * @param blocksCount number of blocks to wait704   * @returns705   */706  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {707    timeout = timeout ?? blocksCount * 60_000;708    // eslint-disable-next-line no-async-promise-executor709    const promise = new Promise<void>(async (resolve) => {710      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {711        if (blocksCount > 0) {712          blocksCount--;713        } else {714          unsubscribe();715          resolve();716        }717      });718    });719    await this.waitWithTimeout(promise, timeout);720    return promise;721  }722723  /**724   * Wait for the specified number of sessions to pass.725   * Only applicable if the Session pallet is turned on.726   * @param sessionCount number of sessions to wait727   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks728   * @returns729   */730  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {731    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`732      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');733734    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;735    let currentSessionIndex = -1;736737    while (currentSessionIndex < expectedSessionIndex) {738      // eslint-disable-next-line no-async-promise-executor739      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {740        await this.newBlocks(1);741        const res = await (this.helper as DevUniqueHelper).session.getIndex();742        resolve(res);743      }), blockTimeout, 'The chain has stopped producing blocks!');744    }745  }746747  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {748    timeout = timeout ?? 30 * 60 * 1000;749    // eslint-disable-next-line no-async-promise-executor750    const promise = new Promise<void>(async (resolve) => {751      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {752        if (data.number.toNumber() >= blockNumber) {753          unsubscribe();754          resolve();755        }756      });757    });758    await this.waitWithTimeout(promise, timeout);759    return promise;760  }761762  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {763    timeout = timeout ?? 30 * 60 * 1000;764    // eslint-disable-next-line no-async-promise-executor765    const promise = new Promise<void>(async (resolve) => {766      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {767        if (data.value.relayParentNumber.toNumber() >= blockNumber) {768          // @ts-ignore769          unsubscribe();770          resolve();771        }772      });773    });774    await this.waitWithTimeout(promise, timeout);775    return promise;776  }777778  noScheduledTasks() {779    const api = this.helper.getApi();780781    // eslint-disable-next-line no-async-promise-executor782    const promise = new Promise<void>(async resolve => {783      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {784        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();785786        if(areThereScheduledTasks.length == 0) {787          unsubscribe();788          resolve();789        }790      });791    });792793    return promise;794  }795796  event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {797    // eslint-disable-next-line no-async-promise-executor798    const promise = new Promise<EventRecord | null>(async (resolve) => {799      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {800        const blockNumber = header.number.toHuman();801        const blockHash = header.hash;802        const eventIdStr = `${eventSection}.${eventMethod}`;803        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;804805        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);806807        const apiAt = await this.helper.getApi().at(blockHash);808        const eventRecords = (await apiAt.query.system.events()) as any;809810        const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => {811          return r.event.section == eventSection && r.event.method == eventMethod;812        });813814        if (neededEvent) {815          unsubscribe();816          resolve(neededEvent);817        } else if (maxBlocksToWait > 0) {818          maxBlocksToWait--;819        } else {820          this.helper.logger.log(`Event \`${eventIdStr}\` is NOT found`);821          unsubscribe();822          resolve(null);823        }824      });825    });826    return promise;827  }828829  async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {830    const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);831832    if (eventRecord == null) {833      return null;834    }835836    const event = eventRecord!.event;837    const data = event.data[fieldIndex] as EventT;838839    return data;840  }841}842843class SessionGroup {844  helper: ChainHelperBase;845846  constructor(helper: ChainHelperBase) {847    this.helper = helper;848  }849850  //todo:collator documentation851  async getIndex(): Promise<number> {852    return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber();853  }854855  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {856    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);857  }858859  setOwnKeys(signer: TSigner, key: string) {860    return this.helper.executeExtrinsic(861      signer,862      'api.tx.session.setKeys',863      [key, '0x0'],864      true,865    );866  }867868  setOwnKeysFromAddress(signer: TSigner) {869    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));870  }871}872873class TestUtilGroup {874  helper: DevUniqueHelper;875876  constructor(helper: DevUniqueHelper) {877    this.helper = helper;878  }879880  async enable() {881    if (this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {882      return;883    }884885    const signer = this.helper.util.fromSeed('//Alice');886    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);887  }888889  async setTestValue(signer: TSigner, testVal: number) {890    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);891  }892893  async incTestValue(signer: TSigner) {894    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);895  }896897  async setTestValueAndRollback(signer: TSigner, testVal: number) {898    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);899  }900901  async testValue(blockIdx?: number) {902    const api = blockIdx903      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))904      : this.helper.getApi();905906    return (await api.query.testUtils.testValue()).toJSON();907  }908909  async justTakeFee(signer: TSigner) {910    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);911  }912913  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {914    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);915  }916}917918class EventCapture {919  helper: DevUniqueHelper;920  eventSection: string;921  eventMethod: string;922  events: EventRecord[] = [];923  unsubscribe: VoidFn | null = null;924925  constructor(926    helper: DevUniqueHelper,927    eventSection: string,928    eventMethod: string,929  ) {930    this.helper = helper;931    this.eventSection = eventSection;932    this.eventMethod = eventMethod;933  }934935  async startCapture() {936    this.stopCapture();937    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {938      const newEvents = eventRecords.filter(r => {939        return r.event.section == this.eventSection && r.event.method == this.eventMethod;940      });941942      this.events.push(...newEvents);943    })) as any;944  }945946  stopCapture() {947    if (this.unsubscribe !== null) {948      this.unsubscribe();949    }950  }951952  extractCapturedEvents() {953    return this.events;954  }955}956957class AdminGroup {958  helper: UniqueHelper;959960  constructor(helper: UniqueHelper) {961    this.helper = helper;962  }963964  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {965    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);966    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => {967      return {968        staker: e.event.data[0].toString(),969        stake: e.event.data[1].toBigInt(),970        payout: e.event.data[2].toBigInt(),971      };972    });973  }974}
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -642,6 +642,10 @@
     return call(...params);
   }
 
+  encodeApiCall(apiCall: string, params: any[]) {
+    return this.constructApiCall(apiCall, params).method.toHex();
+  }
+
   async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {
     if(this.api === null) throw Error('API not initialized');
     if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -15,7 +15,6 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import {blake2AsHex} from '@polkadot/util-crypto';
 import config from '../config';
 import {XcmV2TraitsError} from '../interfaces';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';
@@ -680,11 +679,13 @@
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -738,7 +739,7 @@
       },
     };
 
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,
       {
         Concrete: {
@@ -753,16 +754,55 @@
       testAmount,
     );
 
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Quartz using full QTZ identification
     await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);
+      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    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\'',
+    ).to.be.true;
+
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // 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(
@@ -775,7 +815,7 @@
       'The XCM error should be \'isUntrustedReserveLocation\'',
     ).to.be.true;
 
-    const accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
   });
 });
@@ -845,11 +885,13 @@
 
   const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -918,7 +960,7 @@
 
 describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {
   // Quartz constants
-  let quartzDonor: IKeyringPair;
+  let alice: IKeyringPair;
   let quartzAssetLocation;
 
   let randomAccountQuartz: IKeyringPair;
@@ -927,11 +969,6 @@
   // Moonriver constants
   let assetId: string;
 
-  const councilVotingThreshold = 2;
-  const technicalCommitteeThreshold = 2;
-  const votingPeriod = 3;
-  const delayPeriod = 0;
-
   const quartzAssetMetadata = {
     name: 'xcQuartz',
     symbol: 'xcQTZ',
@@ -952,13 +989,12 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      quartzDonor = await privateKey('//Alice');
-      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);
+      alice = await privateKey('//Alice');
+      [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
 
       balanceForeignQtzTokenInit = 0n;
 
       // Set the default version to wrap the first message to other chains.
-      const alice = quartzDonor;
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
     });
 
@@ -994,84 +1030,13 @@
         unitsPerSecond,
         numAssetsWeightHint,
       });
-      const proposalHash = blake2AsHex(encodedProposal);
 
       console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
-      console.log('Encoded length %d', encodedProposal.length);
-      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
 
-      // >>> Note motion preimage >>>
-      console.log('Note motion preimage.......');
-      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
-      console.log('Note motion preimage.......DONE');
-      // <<< Note motion preimage <<<
-
-      // >>> Propose external motion through council >>>
-      console.log('Propose external motion through council.......');
-      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});
-      const encodedMotion = externalMotion?.method.toHex() || '';
-      const motionHash = blake2AsHex(encodedMotion);
-      console.log('Motion hash is %s', motionHash);
-
-      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);
-
-      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;
-      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
-      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
-
-      await helper.collective.council.close(
-        dorothyAccount,
-        motionHash,
-        councilProposalIdx,
-        {
-          refTime: 1_000_000_000,
-          proofSize: 1_000_000,
-        },
-        externalMotion.encodedLength,
-      );
-      console.log('Propose external motion through council.......DONE');
-      // <<< Propose external motion through council <<<
-
-      // >>> Fast track proposal through technical committee >>>
-      console.log('Fast track proposal through technical committee.......');
-      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
-      const encodedFastTrack = fastTrack?.method.toHex() || '';
-      const fastTrackHash = blake2AsHex(encodedFastTrack);
-      console.log('FastTrack hash is %s', fastTrackHash);
-
-      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
-
-      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;
-      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
-      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
-
-      await helper.collective.techCommittee.close(
-        baltatharAccount,
-        fastTrackHash,
-        techProposalIdx,
-        {
-          refTime: 1_000_000_000,
-          proofSize: 1_000_000,
-        },
-        fastTrack.encodedLength,
-      );
-      console.log('Fast track proposal through technical committee.......DONE');
-      // <<< Fast track proposal through technical committee <<<
-
-      // >>> Referendum voting >>>
-      console.log('Referendum voting.......');
-      await helper.democracy.referendumVote(dorothyAccount, 0, {
-        balance: 10_000_000_000_000_000_000n,
-        vote: {aye: true, conviction: 1},
-      });
-      console.log('Referendum voting.......DONE');
-      // <<< Referendum voting <<<
+      await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
 
       // >>> Acquire Quartz AssetId Info on Moonriver >>>
       console.log('Acquire Quartz AssetId Info on Moonriver.......');
-
-      // Wait for the democracy execute
-      await helper.wait.newBlocks(5);
 
       assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();
 
@@ -1089,7 +1054,7 @@
     });
 
     await usingPlaygrounds(async (helper) => {
-      await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
+      await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
       balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
     });
   });
@@ -1197,14 +1162,197 @@
     expect(qtzFees == 0n).to.be.true;
   });
 
-  // eslint-disable-next-line require-await
-  itSub.skip('Moonriver can send only up to its balance', async ({helper}) => {
-    throw Error('Not yet implemented');
+  itSub('Moonriver can send only up to its balance', async ({helper}) => {
+    // set Moonriver's sovereign account's balance
+    const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);
+    const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);
+    await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);
+
+    const moreThanMoonriverHas = moonriverBalance * 2n;
+
+    let targetAccountBalance = 0n;
+    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
+
+    const quartzMultilocation = {
+      V1: {
+        parents: 1,
+        interior: {
+          X1: {Parachain: QUARTZ_CHAIN},
+        },
+      },
+    };
+
+    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      moreThanMoonriverHas,
+    );
+
+    // Try to trick Quartz
+    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isFailedToTransactAsset,
+      'The XCM error should be \'FailedToTransactAsset\'',
+    ).to.be.true;
+
+    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(targetAccountBalance).to.be.equal(0n);
+
+    // But Moonriver still can send the correct amount
+    const validTransferAmount = moonriverBalance / 2n;
+    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      validTransferAmount,
+    );
+
+    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);
+
+      // Needed to bypass the call filter.
+      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
+      await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);
+    });
+
+    await helper.wait.newBlocks(maxWaitBlocks);
+
+    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(targetAccountBalance).to.be.equal(validTransferAmount);
   });
 
-  // eslint-disable-next-line require-await
-  itSub.skip('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {
-    throw Error('Not yet implemented');
+  itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {
+    const testAmount = 10_000n * (10n ** QTZ_DECIMALS);
+    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
+
+    const quartzMultilocation = {
+      V1: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: QUARTZ_CHAIN,
+          },
+        },
+      },
+    };
+
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: {
+            X1: {
+              Parachain: QUARTZ_CHAIN,
+            },
+          },
+        },
+      },
+      testAmount,
+    );
+
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Quartz using full QTZ identification
+    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // Try to trick Quartz using shortened QTZ identification
+    await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
   });
 });
 
@@ -1456,11 +1604,13 @@
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -1514,7 +1664,7 @@
       },
     };
 
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,
       {
         Concrete: {
@@ -1529,16 +1679,55 @@
       testAmount,
     );
 
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Quartz using full QTZ identification
     await usingShidenPlaygrounds(shidenUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);
+      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,
+    );
+
+    expect(
+      xcmpQueueFailEvent != null,
+      '\'xcmpQueue.FailEvent\' event is expected',
+    ).to.be.true;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // Try to trick Quartz using shortened QTZ identification
+    await usingShidenPlaygrounds(shidenUrl, async (helper) => {
+      await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);
+    });
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -1551,7 +1740,7 @@
       'The XCM error should be \'isUntrustedReserveLocation\'',
     ).to.be.true;
 
-    const accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    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
@@ -15,7 +15,6 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import {blake2AsHex} from '@polkadot/util-crypto';
 import config from '../config';
 import {XcmV2TraitsError} from '../interfaces';
 import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';
@@ -682,11 +681,13 @@
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -725,7 +726,7 @@
     expect(targetAccountBalance).to.be.equal(validTransferAmount);
   });
 
-  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {
+  itSub.only('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {
     const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
     const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
 
@@ -740,7 +741,7 @@
       },
     };
 
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,
       {
         Concrete: {
@@ -755,16 +756,30 @@
       testAmount,
     );
 
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Unique using full UNQ identification
     await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -777,8 +792,33 @@
       'The XCM error should be \'isUntrustedReserveLocation\'',
     ).to.be.true;
 
-    const accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
+
+    // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
   });
 });
 
@@ -847,11 +887,13 @@
 
   const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -920,7 +962,7 @@
 
 describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {
   // Unique constants
-  let uniqueDonor: IKeyringPair;
+  let alice: IKeyringPair;
   let uniqueAssetLocation;
 
   let randomAccountUnique: IKeyringPair;
@@ -929,11 +971,6 @@
   // Moonbeam constants
   let assetId: string;
 
-  const councilVotingThreshold = 2;
-  const technicalCommitteeThreshold = 2;
-  const votingPeriod = 3;
-  const delayPeriod = 0;
-
   const uniqueAssetMetadata = {
     name: 'xcUnique',
     symbol: 'xcUNQ',
@@ -954,13 +991,12 @@
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
-      uniqueDonor = await privateKey('//Alice');
-      [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);
+      alice = await privateKey('//Alice');
+      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);
 
       balanceForeignUnqTokenInit = 0n;
 
       // Set the default version to wrap the first message to other chains.
-      const alice = uniqueDonor;
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
     });
 
@@ -996,84 +1032,13 @@
         unitsPerSecond,
         numAssetsWeightHint,
       });
-      const proposalHash = blake2AsHex(encodedProposal);
 
       console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
-      console.log('Encoded length %d', encodedProposal.length);
-      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
-
-      // >>> Note motion preimage >>>
-      console.log('Note motion preimage.......');
-      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);
-      console.log('Note motion preimage.......DONE');
-      // <<< Note motion preimage <<<
-
-      // >>> Propose external motion through council >>>
-      console.log('Propose external motion through council.......');
-      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});
-      const encodedMotion = externalMotion?.method.toHex() || '';
-      const motionHash = blake2AsHex(encodedMotion);
-      console.log('Motion hash is %s', motionHash);
-
-      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);
-
-      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;
-      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
-      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
-
-      await helper.collective.council.close(
-        dorothyAccount,
-        motionHash,
-        councilProposalIdx,
-        {
-          refTime: 1_000_000_000,
-          proofSize: 1_000_000,
-        },
-        externalMotion.encodedLength,
-      );
-      console.log('Propose external motion through council.......DONE');
-      // <<< Propose external motion through council <<<
 
-      // >>> Fast track proposal through technical committee >>>
-      console.log('Fast track proposal through technical committee.......');
-      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
-      const encodedFastTrack = fastTrack?.method.toHex() || '';
-      const fastTrackHash = blake2AsHex(encodedFastTrack);
-      console.log('FastTrack hash is %s', fastTrackHash);
-
-      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
-
-      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;
-      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
-      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
-
-      await helper.collective.techCommittee.close(
-        baltatharAccount,
-        fastTrackHash,
-        techProposalIdx,
-        {
-          refTime: 1_000_000_000,
-          proofSize: 1_000_000,
-        },
-        fastTrack.encodedLength,
-      );
-      console.log('Fast track proposal through technical committee.......DONE');
-      // <<< Fast track proposal through technical committee <<<
+      await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);
 
-      // >>> Referendum voting >>>
-      console.log('Referendum voting.......');
-      await helper.democracy.referendumVote(dorothyAccount, 0, {
-        balance: 10_000_000_000_000_000_000n,
-        vote: {aye: true, conviction: 1},
-      });
-      console.log('Referendum voting.......DONE');
-      // <<< Referendum voting <<<
-
       // >>> Acquire Unique AssetId Info on Moonbeam >>>
       console.log('Acquire Unique AssetId Info on Moonbeam.......');
-
-      // Wait for the democracy execute
-      await helper.wait.newBlocks(5);
 
       assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
 
@@ -1091,7 +1056,7 @@
     });
 
     await usingPlaygrounds(async (helper) => {
-      await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);
+      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);
       balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
     });
   });
@@ -1200,14 +1165,197 @@
     expect(unqFees == 0n).to.be.true;
   });
 
-  // eslint-disable-next-line require-await
-  itSub.skip('Moonbeam can send only up to its balance', async ({helper}) => {
-    throw Error('Not yet implemented');
+  itSub('Moonbeam can send only up to its balance', async ({helper}) => {
+    // set Moonbeam's sovereign account's balance
+    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);
+    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);
+    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);
+
+    const moreThanMoonbeamHas = moonbeamBalance * 2n;
+
+    let targetAccountBalance = 0n;
+    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
+
+    const uniqueMultilocation = {
+      V1: {
+        parents: 1,
+        interior: {
+          X1: {Parachain: UNIQUE_CHAIN},
+        },
+      },
+    };
+
+    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      moreThanMoonbeamHas,
+    );
+
+    // Try to trick Unique
+    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isFailedToTransactAsset,
+      'The XCM error should be \'FailedToTransactAsset\'',
+    ).to.be.true;
+
+    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(targetAccountBalance).to.be.equal(0n);
+
+    // But Moonbeam still can send the correct amount
+    const validTransferAmount = moonbeamBalance / 2n;
+    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      validTransferAmount,
+    );
+
+    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);
+
+      // Needed to bypass the call filter.
+      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
+      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);
+    });
+
+    await helper.wait.newBlocks(maxWaitBlocks);
+
+    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(targetAccountBalance).to.be.equal(validTransferAmount);
   });
 
-  // eslint-disable-next-line require-await
-  itSub.skip('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {
-    throw Error('Not yet implemented');
+  itSub.only('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {
+    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
+    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
+
+    const uniqueMultilocation = {
+      V1: {
+        parents: 1,
+        interior: {
+          X1: {
+            Parachain: UNIQUE_CHAIN,
+          },
+        },
+      },
+    };
+
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: {
+            X1: {
+              Parachain: UNIQUE_CHAIN,
+            },
+          },
+        },
+      },
+      testAmount,
+    );
+
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Unique using full UNQ identification
+    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // Try to trick Unique using shortened UNQ identification
+    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
+      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);
+
+      // 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;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
+
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
   });
 });
 
@@ -1215,14 +1363,14 @@
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
 
-  const UNQ_ASSET_ID_ON_SHIDEN = 1;
-  const UNQ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
+  const UNQ_ASSET_ID_ON_ASTAR = 1;
+  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;
 
   // Unique -> Astar
-  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Shiden.
+  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
   const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
   const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ
-  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Shiden takes a commision in foreign tokens
+  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens
 
   // Astar -> Unique
   const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ
@@ -1245,14 +1393,14 @@
       // TODO update metadata with values from production
       await helper.assets.create(
         alice,
-        UNQ_ASSET_ID_ON_SHIDEN,
+        UNQ_ASSET_ID_ON_ASTAR,
         alice.address,
-        UNQ_MINIMAL_BALANCE_ON_SHIDEN,
+        UNQ_MINIMAL_BALANCE_ON_ASTAR,
       );
 
       await helper.assets.setMetadata(
         alice,
-        UNQ_ASSET_ID_ON_SHIDEN,
+        UNQ_ASSET_ID_ON_ASTAR,
         'Cross chain UNQ',
         'xcUNQ',
         Number(UNQ_DECIMALS),
@@ -1270,7 +1418,7 @@
         },
       };
 
-      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_SHIDEN]);
+      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);
 
       console.log('3. Set UNQ payment for XCM execution on Astar');
       await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
@@ -1337,7 +1485,7 @@
 
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
       await helper.wait.newBlocks(3);
-      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);
+      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);
       const astarBalance = await helper.balance.getSubstrate(randomAccount.address);
 
       console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);
@@ -1405,7 +1553,7 @@
       // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw
       await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);
 
-      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);
+      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);
       const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);
       console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);
 
@@ -1427,7 +1575,7 @@
     const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);
     await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);
 
-    const moreThanShidenHas = astarBalance * 2n;
+    const moreThanAstarHas = astarBalance * 2n;
 
     let targetAccountBalance = 0n;
     const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
@@ -1449,7 +1597,7 @@
           interior: 'Here',
         },
       },
-      moreThanShidenHas,
+      moreThanAstarHas,
     );
 
     // Try to trick Unique
@@ -1458,11 +1606,13 @@
     });
 
     const maxWaitBlocks = 3;
+    const outcomeField = 1;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
       maxWaitBlocks,
       'xcmpQueue',
       'Fail',
+      outcomeField,
     );
 
     expect(
@@ -1501,7 +1651,7 @@
     expect(targetAccountBalance).to.be.equal(validTransferAmount);
   });
 
-  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {
+  itSub.only('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {
     const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
     const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
 
@@ -1516,7 +1666,7 @@
       },
     };
 
-    const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(
+    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
       targetAccount.addressRaw,
       {
         Concrete: {
@@ -1531,16 +1681,55 @@
       testAmount,
     );
 
+    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
+      targetAccount.addressRaw,
+      {
+        Concrete: {
+          parents: 0,
+          interior: 'Here',
+        },
+      },
+      testAmount,
+    );
+
+    // Try to trick Unique using full UNQ identification
     await usingAstarPlaygrounds(astarUrl, async (helper) => {
-      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);
+      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,
+    );
+
+    expect(
+      xcmpQueueFailEvent != null,
+      '\'xcmpQueue.FailEvent\' event is expected',
+    ).to.be.true;
+
+    expect(
+      xcmpQueueFailEvent!.isUntrustedReserveLocation,
+      'The XCM error should be \'isUntrustedReserveLocation\'',
+    ).to.be.true;
 
-    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(
+    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    expect(accountBalance).to.be.equal(0n);
+
+    // 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(
@@ -1553,7 +1742,7 @@
       'The XCM error should be \'isUntrustedReserveLocation\'',
     ).to.be.true;
 
-    const accountBalance = await helper.balance.getSubstrate(targetAccount.address);
+    accountBalance = await helper.balance.getSubstrate(targetAccount.address);
     expect(accountBalance).to.be.equal(0n);
   });