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
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 import {stringToU8a} from '@polkadot/util';
-import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
+import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
 import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique';
 import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
 import * as defs from '../../interfaces/definitions';
@@ -155,6 +155,7 @@
 export class DevMoonbeamHelper extends MoonbeamHelper {
   account: MoonbeamAccountGroup;
   wait: WaitGroup;
+  fastDemocracy: MoonbeamFastDemocracyGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? DevMoonbeamHelper;
@@ -163,6 +164,7 @@
     super(logger, options);
     this.account = new MoonbeamAccountGroup(this);
     this.wait = new WaitGroup(this);
+    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);
   }
 }
 
@@ -554,6 +556,103 @@
   }
 }
 
+class MoonbeamFastDemocracyGroup {
+  helper: DevMoonbeamHelper;
+
+  constructor(helper: DevMoonbeamHelper) {
+    this.helper = helper;
+  }
+
+  async executeProposal(proposalDesciption: string, encodedProposal: string) {
+    const proposalHash = blake2AsHex(encodedProposal);
+
+    const alithAccount = this.helper.account.alithAccount();
+    const baltatharAccount = this.helper.account.baltatharAccount();
+    const dorothyAccount = this.helper.account.dorothyAccount();
+
+    const councilVotingThreshold = 2;
+    const technicalCommitteeThreshold = 2;
+    const fastTrackVotingPeriod = 3;
+    const fastTrackDelayPeriod = 0;
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
+
+    // >>> Propose external motion through council >>>
+    console.log('\t* Propose external motion through council.......');
+    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
+    const encodedMotion = externalMotion?.method.toHex() || '';
+    const motionHash = blake2AsHex(encodedMotion);
+    console.log('\t* Motion hash is %s', motionHash);
+
+    await this.helper.collective.council.propose(
+      baltatharAccount,
+      councilVotingThreshold,
+      externalMotion,
+      externalMotion.encodedLength,
+    );
+
+    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;
+    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
+    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
+
+    await this.helper.collective.council.close(
+      dorothyAccount,
+      motionHash,
+      councilProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      externalMotion.encodedLength,
+    );
+    console.log('\t* Propose external motion through council.......DONE');
+    // <<< Propose external motion through council <<<
+
+    // >>> Fast track proposal through technical committee >>>
+    console.log('\t* Fast track proposal through technical committee.......');
+    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
+    const encodedFastTrack = fastTrack?.method.toHex() || '';
+    const fastTrackHash = blake2AsHex(encodedFastTrack);
+    console.log('\t* FastTrack hash is %s', fastTrackHash);
+
+    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
+
+    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
+    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
+
+    await this.helper.collective.techCommittee.close(
+      baltatharAccount,
+      fastTrackHash,
+      techProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      fastTrack.encodedLength,
+    );
+    console.log('\t* Fast track proposal through technical committee.......DONE');
+    // <<< Fast track proposal through technical committee <<<
+
+    const refIndexField = 0;
+    const referendumIndex = await this.helper.wait.eventData<any>(3, 'democracy', 'Started', refIndexField);
+
+    // >>> Referendum voting >>>
+    console.log(`\t* Referendum #${referendumIndex} voting.......`);
+    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {
+      balance: 10_000_000_000_000_000_000n,
+      vote: {aye: true, conviction: 1},
+    });
+    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
+    // <<< Referendum voting <<<
+
+    // Wait for the democracy execute
+    await this.helper.wait.newBlocks(5);
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
+  }
+}
+
 class WaitGroup {
   helper: ChainHelperBase;
 
@@ -727,7 +826,7 @@
     return promise;
   }
 
-  async eventOutcome<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
+  async eventData<EventT>(maxBlocksToWait: number, eventSection: string, eventMethod: string, fieldIndex: number) {
     const eventRecord = await this.event(maxBlocksToWait, eventSection, eventMethod);
 
     if (eventRecord == null) {
@@ -735,9 +834,9 @@
     }
 
     const event = eventRecord!.event;
-    const outcome = event.data[1] as EventT;
+    const data = event.data[fieldIndex] as EventT;
 
-    return outcome;
+    return data;
   }
 }
 
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
15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
1616
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import {blake2AsHex} from '@polkadot/util-crypto';
19import config from '../config';18import config from '../config';
20import {XcmV2TraitsError} from '../interfaces';19import {XcmV2TraitsError} from '../interfaces';
21import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';20import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';
682 });681 });
683682
684 const maxWaitBlocks = 3;683 const maxWaitBlocks = 3;
684 const outcomeField = 1;
685685
686 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(686 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
687 maxWaitBlocks,687 maxWaitBlocks,
688 'xcmpQueue',688 'xcmpQueue',
689 'Fail',689 'Fail',
690 outcomeField,
690 );691 );
691692
692 expect(693 expect(
725 expect(targetAccountBalance).to.be.equal(validTransferAmount);726 expect(targetAccountBalance).to.be.equal(validTransferAmount);
726 });727 });
727728
728 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {729 itSub.only('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {
729 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);730 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
730 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);731 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
731732
740 },741 },
741 };742 };
742743
743 const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(744 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
744 targetAccount.addressRaw,745 targetAccount.addressRaw,
745 {746 {
746 Concrete: {747 Concrete: {
755 testAmount,756 testAmount,
756 );757 );
757758
759 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
760 targetAccount.addressRaw,
761 {
762 Concrete: {
763 parents: 0,
764 interior: 'Here',
765 },
766 },
767 testAmount,
768 );
769
770 // Try to trick Unique using full UNQ identification
758 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {771 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
759 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);772 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
760 });773 });
761774
762 const maxWaitBlocks = 3;775 const maxWaitBlocks = 3;
763
764 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(776 const outcomeField = 1;
777
778 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
765 maxWaitBlocks,779 maxWaitBlocks,
766 'xcmpQueue',780 'xcmpQueue',
767 'Fail',781 'Fail',
782 outcomeField,
768 );783 );
769784
770 expect(785 expect(
777 'The XCM error should be \'isUntrustedReserveLocation\'',792 'The XCM error should be \'isUntrustedReserveLocation\'',
778 ).to.be.true;793 ).to.be.true;
779794
780 const accountBalance = await helper.balance.getSubstrate(targetAccount.address);795 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
781 expect(accountBalance).to.be.equal(0n);796 expect(accountBalance).to.be.equal(0n);
797
798 // Try to trick Unique using shortened UNQ identification
799 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
800 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
801 });
802
803 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
804 maxWaitBlocks,
805 'xcmpQueue',
806 'Fail',
807 outcomeField,
808 );
809
810 expect(
811 xcmpQueueFailEvent != null,
812 '\'xcmpQueue.FailEvent\' event is expected',
813 ).to.be.true;
814
815 expect(
816 xcmpQueueFailEvent!.isUntrustedReserveLocation,
817 'The XCM error should be \'isUntrustedReserveLocation\'',
818 ).to.be.true;
819
820 accountBalance = await helper.balance.getSubstrate(targetAccount.address);
821 expect(accountBalance).to.be.equal(0n);
782 });822 });
783});823});
784824
847887
848 const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {888 const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {
849 const maxWaitBlocks = 3;889 const maxWaitBlocks = 3;
890 const outcomeField = 1;
850891
851 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(892 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
852 maxWaitBlocks,893 maxWaitBlocks,
853 'xcmpQueue',894 'xcmpQueue',
854 'Fail',895 'Fail',
896 outcomeField,
855 );897 );
856898
857 expect(899 expect(
920962
921describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {963describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {
922 // Unique constants964 // Unique constants
923 let uniqueDonor: IKeyringPair;965 let alice: IKeyringPair;
924 let uniqueAssetLocation;966 let uniqueAssetLocation;
925967
926 let randomAccountUnique: IKeyringPair;968 let randomAccountUnique: IKeyringPair;
929 // Moonbeam constants971 // Moonbeam constants
930 let assetId: string;972 let assetId: string;
931
932 const councilVotingThreshold = 2;
933 const technicalCommitteeThreshold = 2;
934 const votingPeriod = 3;
935 const delayPeriod = 0;
936973
937 const uniqueAssetMetadata = {974 const uniqueAssetMetadata = {
938 name: 'xcUnique',975 name: 'xcUnique',
954991
955 before(async () => {992 before(async () => {
956 await usingPlaygrounds(async (helper, privateKey) => {993 await usingPlaygrounds(async (helper, privateKey) => {
957 uniqueDonor = await privateKey('//Alice');994 alice = await privateKey('//Alice');
958 [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);995 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);
959996
960 balanceForeignUnqTokenInit = 0n;997 balanceForeignUnqTokenInit = 0n;
961998
962 // Set the default version to wrap the first message to other chains.999 // Set the default version to wrap the first message to other chains.
963 const alice = uniqueDonor;
964 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1000 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
965 });1001 });
9661002
996 unitsPerSecond,1032 unitsPerSecond,
997 numAssetsWeightHint,1033 numAssetsWeightHint,
998 });1034 });
999 const proposalHash = blake2AsHex(encodedProposal);
10001035
1001 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);1036 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
1002 console.log('Encoded length %d', encodedProposal.length);1037
1003 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);
1004
1005 // >>> Note motion preimage >>>
1006 console.log('Note motion preimage.......');
1007 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);1038 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);
1008 console.log('Note motion preimage.......DONE');
1009 // <<< Note motion preimage <<<
1010
1011 // >>> Propose external motion through council >>>
1012 console.log('Propose external motion through council.......');
1013 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});
1014 const encodedMotion = externalMotion?.method.toHex() || '';
1015 const motionHash = blake2AsHex(encodedMotion);
1016 console.log('Motion hash is %s', motionHash);
1017
1018 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);
1019
1020 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;
1021 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
1022 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
1023
1024 await helper.collective.council.close(
1025 dorothyAccount,
1026 motionHash,
1027 councilProposalIdx,
1028 {
1029 refTime: 1_000_000_000,
1030 proofSize: 1_000_000,
1031 },
1032 externalMotion.encodedLength,
1033 );
1034 console.log('Propose external motion through council.......DONE');
1035 // <<< Propose external motion through council <<<
1036
1037 // >>> Fast track proposal through technical committee >>>
1038 console.log('Fast track proposal through technical committee.......');
1039 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);
1040 const encodedFastTrack = fastTrack?.method.toHex() || '';
1041 const fastTrackHash = blake2AsHex(encodedFastTrack);
1042 console.log('FastTrack hash is %s', fastTrackHash);
1043
1044 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
1045
1046 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;
1047 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
1048 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
1049
1050 await helper.collective.techCommittee.close(
1051 baltatharAccount,
1052 fastTrackHash,
1053 techProposalIdx,
1054 {
1055 refTime: 1_000_000_000,
1056 proofSize: 1_000_000,
1057 },
1058 fastTrack.encodedLength,
1059 );
1060 console.log('Fast track proposal through technical committee.......DONE');
1061 // <<< Fast track proposal through technical committee <<<
1062
1063 // >>> Referendum voting >>>
1064 console.log('Referendum voting.......');
1065 await helper.democracy.referendumVote(dorothyAccount, 0, {
1066 balance: 10_000_000_000_000_000_000n,
1067 vote: {aye: true, conviction: 1},
1068 });
1069 console.log('Referendum voting.......DONE');
1070 // <<< Referendum voting <<<
10711039
1072 // >>> Acquire Unique AssetId Info on Moonbeam >>>1040 // >>> Acquire Unique AssetId Info on Moonbeam >>>
1073 console.log('Acquire Unique AssetId Info on Moonbeam.......');1041 console.log('Acquire Unique AssetId Info on Moonbeam.......');
1074
1075 // Wait for the democracy execute
1076 await helper.wait.newBlocks(5);
10771042
1078 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1043 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();
10791044
1091 });1056 });
10921057
1093 await usingPlaygrounds(async (helper) => {1058 await usingPlaygrounds(async (helper) => {
1094 await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1059 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);
1095 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1060 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
1096 });1061 });
1097 });1062 });
1200 expect(unqFees == 0n).to.be.true;1165 expect(unqFees == 0n).to.be.true;
1201 });1166 });
12021167
1203 // eslint-disable-next-line require-await
1204 itSub.skip('Moonbeam can send only up to its balance', async ({helper}) => {1168 itSub('Moonbeam can send only up to its balance', async ({helper}) => {
1169 // set Moonbeam's sovereign account's balance
1170 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);
1171 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);
1172 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);
1173
1174 const moreThanMoonbeamHas = moonbeamBalance * 2n;
1175
1176 let targetAccountBalance = 0n;
1177 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
1178
1179 const uniqueMultilocation = {
1180 V1: {
1181 parents: 1,
1182 interior: {
1183 X1: {Parachain: UNIQUE_CHAIN},
1184 },
1185 },
1186 };
1187
1188 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
1189 targetAccount.addressRaw,
1190 {
1191 Concrete: {
1192 parents: 0,
1193 interior: 'Here',
1194 },
1195 },
1196 moreThanMoonbeamHas,
1197 );
1198
1199 // Try to trick Unique
1200 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
1201 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);
1202
1203 // Needed to bypass the call filter.
1204 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
1205 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);
1206 });
1207
1208 const maxWaitBlocks = 3;
1209 const outcomeField = 1;
1210
1205 throw Error('Not yet implemented');1211 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1212 maxWaitBlocks,
1213 'xcmpQueue',
1214 'Fail',
1215 outcomeField,
1216 );
1217
1218 expect(
1219 xcmpQueueFailEvent != null,
1220 '\'xcmpQueue.FailEvent\' event is expected',
1221 ).to.be.true;
1222
1223 expect(
1224 xcmpQueueFailEvent!.isFailedToTransactAsset,
1225 'The XCM error should be \'FailedToTransactAsset\'',
1226 ).to.be.true;
1227
1228 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
1229 expect(targetAccountBalance).to.be.equal(0n);
1230
1231 // But Moonbeam still can send the correct amount
1232 const validTransferAmount = moonbeamBalance / 2n;
1233 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
1234 targetAccount.addressRaw,
1235 {
1236 Concrete: {
1237 parents: 0,
1238 interior: 'Here',
1239 },
1240 },
1241 validTransferAmount,
1242 );
1243
1244 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
1245 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);
1246
1247 // Needed to bypass the call filter.
1248 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
1249 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);
1250 });
1251
1252 await helper.wait.newBlocks(maxWaitBlocks);
1253
1254 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
1255 expect(targetAccountBalance).to.be.equal(validTransferAmount);
1206 });1256 });
12071257
1208 // eslint-disable-next-line require-await
1209 itSub.skip('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1258 itSub.only('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {
1259 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
1260 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
1261
1262 const uniqueMultilocation = {
1263 V1: {
1264 parents: 1,
1265 interior: {
1266 X1: {
1267 Parachain: UNIQUE_CHAIN,
1268 },
1269 },
1270 },
1271 };
1272
1273 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
1274 targetAccount.addressRaw,
1275 {
1276 Concrete: {
1277 parents: 0,
1278 interior: {
1279 X1: {
1280 Parachain: UNIQUE_CHAIN,
1281 },
1282 },
1283 },
1284 },
1285 testAmount,
1286 );
1287
1288 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
1289 targetAccount.addressRaw,
1290 {
1291 Concrete: {
1292 parents: 0,
1293 interior: 'Here',
1294 },
1295 },
1296 testAmount,
1297 );
1298
1299 // Try to trick Unique using full UNQ identification
1300 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
1301 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);
1302
1303 // Needed to bypass the call filter.
1304 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
1305 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);
1306 });
1307
1308 const maxWaitBlocks = 3;
1309 const outcomeField = 1;
1310
1210 throw Error('Not yet implemented');1311 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1312 maxWaitBlocks,
1313 'xcmpQueue',
1314 'Fail',
1315 outcomeField,
1316 );
1317
1318 expect(
1319 xcmpQueueFailEvent != null,
1320 '\'xcmpQueue.FailEvent\' event is expected',
1321 ).to.be.true;
1322
1323 expect(
1324 xcmpQueueFailEvent!.isUntrustedReserveLocation,
1325 'The XCM error should be \'isUntrustedReserveLocation\'',
1326 ).to.be.true;
1327
1328 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
1329 expect(accountBalance).to.be.equal(0n);
1330
1331 // Try to trick Unique using shortened UNQ identification
1332 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
1333 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);
1334
1335 // Needed to bypass the call filter.
1336 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
1337 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);
1338 });
1339
1340 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1341 maxWaitBlocks,
1342 'xcmpQueue',
1343 'Fail',
1344 outcomeField,
1345 );
1346
1347 expect(
1348 xcmpQueueFailEvent != null,
1349 '\'xcmpQueue.FailEvent\' event is expected',
1350 ).to.be.true;
1351
1352 expect(
1353 xcmpQueueFailEvent!.isUntrustedReserveLocation,
1354 'The XCM error should be \'isUntrustedReserveLocation\'',
1355 ).to.be.true;
1356
1357 accountBalance = await helper.balance.getSubstrate(targetAccount.address);
1358 expect(accountBalance).to.be.equal(0n);
1211 });1359 });
1212});1360});
12131361
1214describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1362describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {
1215 let alice: IKeyringPair;1363 let alice: IKeyringPair;
1216 let randomAccount: IKeyringPair;1364 let randomAccount: IKeyringPair;
12171365
1218 const UNQ_ASSET_ID_ON_SHIDEN = 1;1366 const UNQ_ASSET_ID_ON_ASTAR = 1;
1219 const UNQ_MINIMAL_BALANCE_ON_SHIDEN = 1n;1367 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;
12201368
1221 // Unique -> Astar1369 // Unique -> Astar
1222 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Shiden.1370 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.
1223 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1371 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
1224 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1372 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ
1225 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Shiden takes a commision in foreign tokens1373 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens
12261374
1227 // Astar -> Unique1375 // Astar -> Unique
1228 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1376 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ
1245 // TODO update metadata with values from production1393 // TODO update metadata with values from production
1246 await helper.assets.create(1394 await helper.assets.create(
1247 alice,1395 alice,
1248 UNQ_ASSET_ID_ON_SHIDEN,1396 UNQ_ASSET_ID_ON_ASTAR,
1249 alice.address,1397 alice.address,
1250 UNQ_MINIMAL_BALANCE_ON_SHIDEN,1398 UNQ_MINIMAL_BALANCE_ON_ASTAR,
1251 );1399 );
12521400
1253 await helper.assets.setMetadata(1401 await helper.assets.setMetadata(
1254 alice,1402 alice,
1255 UNQ_ASSET_ID_ON_SHIDEN,1403 UNQ_ASSET_ID_ON_ASTAR,
1256 'Cross chain UNQ',1404 'Cross chain UNQ',
1257 'xcUNQ',1405 'xcUNQ',
1258 Number(UNQ_DECIMALS),1406 Number(UNQ_DECIMALS),
1270 },1418 },
1271 };1419 };
12721420
1273 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_SHIDEN]);1421 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);
12741422
1275 console.log('3. Set UNQ payment for XCM execution on Astar');1423 console.log('3. Set UNQ payment for XCM execution on Astar');
1276 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1424 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
13371485
1338 await usingAstarPlaygrounds(astarUrl, async (helper) => {1486 await usingAstarPlaygrounds(astarUrl, async (helper) => {
1339 await helper.wait.newBlocks(3);1487 await helper.wait.newBlocks(3);
1340 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1488 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);
1341 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);1489 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);
13421490
1343 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1491 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);
1405 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1553 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw
1406 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);1554 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);
14071555
1408 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1556 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);
1409 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1557 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);
1410 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1558 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);
14111559
1427 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1575 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);
1428 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);1576 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);
14291577
1430 const moreThanShidenHas = astarBalance * 2n;1578 const moreThanAstarHas = astarBalance * 2n;
14311579
1432 let targetAccountBalance = 0n;1580 let targetAccountBalance = 0n;
1433 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);1581 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);
1449 interior: 'Here',1597 interior: 'Here',
1450 },1598 },
1451 },1599 },
1452 moreThanShidenHas,1600 moreThanAstarHas,
1453 );1601 );
14541602
1455 // Try to trick Unique1603 // Try to trick Unique
1458 });1606 });
14591607
1460 const maxWaitBlocks = 3;1608 const maxWaitBlocks = 3;
1609 const outcomeField = 1;
14611610
1462 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(1611 const xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1463 maxWaitBlocks,1612 maxWaitBlocks,
1464 'xcmpQueue',1613 'xcmpQueue',
1465 'Fail',1614 'Fail',
1615 outcomeField,
1466 );1616 );
14671617
1468 expect(1618 expect(
1501 expect(targetAccountBalance).to.be.equal(validTransferAmount);1651 expect(targetAccountBalance).to.be.equal(validTransferAmount);
1502 });1652 });
15031653
1504 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1654 itSub.only('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {
1505 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1655 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
1506 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);1656 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);
15071657
1516 },1666 },
1517 };1667 };
15181668
1519 const maliciousXcmProgram = helper.arrange.makeXcmProgramReserveAssetDeposited(1669 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
1520 targetAccount.addressRaw,1670 targetAccount.addressRaw,
1521 {1671 {
1522 Concrete: {1672 Concrete: {
1531 testAmount,1681 testAmount,
1532 );1682 );
15331683
1684 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
1685 targetAccount.addressRaw,
1686 {
1687 Concrete: {
1688 parents: 0,
1689 interior: 'Here',
1690 },
1691 },
1692 testAmount,
1693 );
1694
1695 // Try to trick Unique using full UNQ identification
1534 await usingAstarPlaygrounds(astarUrl, async (helper) => {1696 await usingAstarPlaygrounds(astarUrl, async (helper) => {
1535 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);1697 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);
1536 });1698 });
15371699
1538 const maxWaitBlocks = 3;1700 const maxWaitBlocks = 3;
1539
1540 const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(1701 const outcomeField = 1;
1702
1703 let xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1541 maxWaitBlocks,1704 maxWaitBlocks,
1542 'xcmpQueue',1705 'xcmpQueue',
1543 'Fail',1706 'Fail',
1707 outcomeField,
1544 );1708 );
15451709
1546 expect(1710 expect(
1553 'The XCM error should be \'isUntrustedReserveLocation\'',1717 'The XCM error should be \'isUntrustedReserveLocation\'',
1554 ).to.be.true;1718 ).to.be.true;
15551719
1556 const accountBalance = await helper.balance.getSubstrate(targetAccount.address);1720 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
1557 expect(accountBalance).to.be.equal(0n);1721 expect(accountBalance).to.be.equal(0n);
1722
1723 // Try to trick Unique using shortened UNQ identification
1724 await usingAstarPlaygrounds(astarUrl, async (helper) => {
1725 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);
1726 });
1727
1728 xcmpQueueFailEvent = await helper.wait.eventData<XcmV2TraitsError>(
1729 maxWaitBlocks,
1730 'xcmpQueue',
1731 'Fail',
1732 outcomeField,
1733 );
1734
1735 expect(
1736 xcmpQueueFailEvent != null,
1737 '\'xcmpQueue.FailEvent\' event is expected',
1738 ).to.be.true;
1739
1740 expect(
1741 xcmpQueueFailEvent!.isUntrustedReserveLocation,
1742 'The XCM error should be \'isUntrustedReserveLocation\'',
1743 ).to.be.true;
1744
1745 accountBalance = await helper.balance.getSubstrate(targetAccount.address);
1746 expect(accountBalance).to.be.equal(0n);
1558 });1747 });
15591748
1560});1749});