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

difftreelog

Merge pull request #801 from UniqueNetwork/fix/xcm-qtz-unq-tests

Yaroslav Bolyukin2022-12-22parents: #b9448b5 #3e66406.patch.diff
in: master
Fix xcm

10 files changed

modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -161,9 +161,7 @@
 
 	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {
 		log::trace!(target: "fassets::get_currency_id", "call");
-		Some(AssetIds::ForeignAssetId(
-			Pallet::<T>::location_to_currency_ids(multi_location).unwrap_or(0),
-		))
+		Pallet::<T>::location_to_currency_ids(multi_location).map(|id| AssetIds::ForeignAssetId(id))
 	}
 }
 
modifiedruntime/common/config/xcm/foreignassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/foreignassets.rs
+++ b/runtime/common/config/xcm/foreignassets.rs
@@ -18,7 +18,7 @@
 	traits::{Contains, Get, fungibles},
 	parameter_types,
 };
-use sp_runtime::traits::{Zero, Convert};
+use sp_runtime::traits::Convert;
 use xcm::v1::{Junction::*, MultiLocation, Junctions::*};
 use xcm::latest::MultiAsset;
 use xcm_builder::{FungiblesAdapter, ConvertedConcreteAssetId};
@@ -38,16 +38,16 @@
 	pub CheckingAccount: AccountId = PolkadotXcm::check_account();
 }
 
-/// Allow checking in assets that have issuance > 0.
-pub struct NonZeroIssuance<AccountId, ForeignAssets>(PhantomData<(AccountId, ForeignAssets)>);
+/// No teleports are allowed
+pub struct NoTeleports<AccountId, ForeignAssets>(PhantomData<(AccountId, ForeignAssets)>);
 
 impl<AccountId, ForeignAssets> Contains<<ForeignAssets as fungibles::Inspect<AccountId>>::AssetId>
-	for NonZeroIssuance<AccountId, ForeignAssets>
+	for NoTeleports<AccountId, ForeignAssets>
 where
 	ForeignAssets: fungibles::Inspect<AccountId>,
 {
-	fn contains(id: &<ForeignAssets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
-		!ForeignAssets::total_issuance(*id).is_zero()
+	fn contains(_id: &<ForeignAssets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
+		false
 	}
 }
 
@@ -84,7 +84,7 @@
 			Some(AssetIds::ForeignAssetId(foreign_asset_id)) => {
 				ConvertAssetId::convert_ref(AssetIds::ForeignAssetId(foreign_asset_id))
 			}
-			_ => ConvertAssetId::convert_ref(AssetIds::ForeignAssetId(0)),
+			_ => Err(()),
 		}
 	}
 
@@ -132,9 +132,8 @@
 	LocationToAccountId,
 	// Our chain's account ID type (we can't get away without mentioning it explicitly):
 	AccountId,
-	// We only want to allow teleports of known assets. We use non-zero issuance as an indication
-	// that this asset is known.
-	NonZeroIssuance<AccountId, ForeignAssets>,
+	// No teleports are allowed
+	NoTeleports<AccountId, ForeignAssets>,
 	// The account to use for tracking teleports.
 	CheckingAccount,
 >;
modifiedruntime/common/config/xcm/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/mod.rs
+++ b/runtime/common/config/xcm/mod.rs
@@ -186,6 +186,9 @@
 			TransferReserveAsset { dest: dst, .. } => {
 				allowed |= allowed_locations.contains(dst);
 			}
+			InitiateReserveWithdraw { reserve: dst, .. } => {
+				allowed |= allowed_locations.contains(dst);
+			}
 			_ => {}
 		});
 
modifiedtests/src/config.tsdiffbeforeafterboth
--- a/tests/src/config.ts
+++ b/tests/src/config.ts
@@ -25,6 +25,8 @@
   moonbeamUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947',
   moonriverUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947',
   westmintUrl: process.env.westmintUrl || 'ws://127.0.0.1:9948',
+  statemineUrl: process.env.statemineUrl || 'ws://127.0.0.1:9948',
+  statemintUrl: process.env.statemintUrl || 'ws://127.0.0.1:9948',
 };
 
 export default config;
modifiedtests/src/util/index.tsdiffbeforeafterboth
--- a/tests/src/util/index.ts
+++ b/tests/src/util/index.ts
@@ -11,7 +11,7 @@
 import config from '../config';
 import {ChainHelperBase} from './playgrounds/unique';
 import {ILogger} from './playgrounds/types';
-import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper} from './playgrounds/unique.dev';
+import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper} from './playgrounds/unique.dev';
 
 chai.use(chaiAsPromised);
 chai.use(chaiSubset);
@@ -65,6 +65,14 @@
   return usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);
 };
 
+export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
+  return usingPlaygroundsGeneral<DevStatemineHelper>(DevWestmintHelper, url, code);
+};
+
+export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
+  return usingPlaygroundsGeneral<DevStatemintHelper>(DevWestmintHelper, url, code);
+};
+
 export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => {
   return usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);
 };
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -118,7 +118,16 @@
   }
 }
 
-export class DevRelayHelper extends RelayHelper {}
+export class DevRelayHelper extends RelayHelper {
+  wait: WaitGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevRelayHelper;
+
+    super(logger, options);
+    this.wait = new WaitGroup(this);
+  }
+}
 
 export class DevWestmintHelper extends WestmintHelper {
   wait: WaitGroup;
@@ -131,12 +140,17 @@
   }
 }
 
+export class DevStatemineHelper extends DevWestmintHelper {}
+
+export class DevStatemintHelper extends DevWestmintHelper {}
+
 export class DevMoonbeamHelper extends MoonbeamHelper {
   account: MoonbeamAccountGroup;
   wait: WaitGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? DevMoonbeamHelper;
+    options.notePreimagePallet = options.notePreimagePallet ?? 'democracy';
 
     super(logger, options);
     this.account = new MoonbeamAccountGroup(this);
@@ -144,7 +158,12 @@
   }
 }
 
-export class DevMoonriverHelper extends DevMoonbeamHelper {}
+export class DevMoonriverHelper extends DevMoonbeamHelper {
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+    super(logger, options);
+  }
+}
 
 export class DevAcalaHelper extends AcalaHelper {
   wait: WaitGroup;
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -2740,21 +2740,72 @@
     this.palletName = palletName;
   }
 
-  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);
+  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);
+  }
+
+  async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);
+  }
+
+  async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint) {
+    const destination = {
+      V1: {
+        parents: 0,
+        interior: {
+          X1: {
+            Parachain: destinationParaId,
+          },
+        },
+      },
+    };
+
+    const beneficiary = {
+      V1: {
+        parents: 0,
+        interior: {
+          X1: {
+            AccountId32: {
+              network: 'Any',
+              id: targetAccount,
+            },
+          },
+        },
+      },
+    };
+
+    const assets = {
+      V1: [
+        {
+          id: {
+            Concrete: {
+              parents: 0,
+              interior: 'Here',
+            },
+          },
+          fun: {
+            Fungible: amount,
+          },
+        },
+      ],
+    };
+
+    const feeAssetItem = 0;
+
+    await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);
   }
 }
 
 class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {
+  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {
     await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
   }
 
-  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {
+  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {
     await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
   }
 
-  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {
+  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {
     await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
   }
 }
@@ -2823,12 +2874,19 @@
 }
 
 class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
+  notePreimagePallet: string;
+
+  constructor(helper: MoonbeamHelper, options: {[key: string]: any} = {}) {
+    super(helper);
+    this.notePreimagePallet = options.notePreimagePallet;
+  }
+
   async notePreimage(signer: TSigner, encodedProposal: string) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
   }
 
-  externalProposeMajority(proposalHash: string) {
-    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);
+  externalProposeMajority(proposal: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
   }
 
   fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
@@ -2857,7 +2915,7 @@
     await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
   }
 
-  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {
+  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
     await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
   }
 
@@ -2917,11 +2975,13 @@
 }
 
 export class RelayHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<RelayHelper>;
   xcm: XcmGroup<RelayHelper>;
 
   constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
     super(logger, options.helperBase ?? RelayHelper);
 
+    this.balance = new SubstrateBalanceGroup(this);
     this.xcm = new XcmGroup(this, 'xcmPallet');
   }
 }
@@ -2960,7 +3020,7 @@
     this.assetManager = new MoonbeamAssetManagerGroup(this);
     this.assets = new AssetsGroup(this);
     this.xTokens = new XTokensGroup(this);
-    this.democracy = new MoonbeamDemocracyGroup(this);
+    this.democracy = new MoonbeamDemocracyGroup(this, options);
     this.collective = {
       council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
       techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
modifiedtests/src/xcm/xcmOpal.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmOpal.test.ts
+++ b/tests/src/xcm/xcmOpal.test.ts
@@ -31,6 +31,7 @@
 const ASSET_METADATA_DESCRIPTION = 'USDT';
 const ASSET_METADATA_MINIMAL_BALANCE = 1n;
 
+const RELAY_DECIMALS = 12;
 const WESTMINT_DECIMALS = 12;
 
 const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n;
@@ -147,9 +148,8 @@
       };
 
       const feeAssetItem = 0;
-      const weightLimit = 5_000_000_000;
 
-      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
+      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
     });
   
   });
@@ -202,16 +202,15 @@
       };
 
       const feeAssetItem = 0;
-      const weightLimit = 5000000000;
 
       balanceStmnBefore = await helper.balance.getSubstrate(alice.address);
-      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, weightLimit);
+      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');
 
       balanceStmnAfter = await helper.balance.getSubstrate(alice.address);
 
       // common good parachain take commission in it native token
       console.log(
-        'Opal to Westmint transaction fees on Westmint: %s WND',
+        '[Westmint -> Opal] transaction fees on Westmint: %s WND',
         helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, WESTMINT_DECIMALS),
       );
       expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
@@ -227,18 +226,19 @@
 
     balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
 
-    // commission has not paid in USDT token
-    expect(free == TRANSFER_AMOUNT).to.be.true;
     console.log(
-      'Opal to Westmint transaction fees on Opal: %s USDT',
-      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free),
+      '[Westmint -> Opal] transaction fees on Opal: %s USDT',
+      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, ASSET_METADATA_DECIMALS),
     );
+    console.log(
+      '[Westmint -> Opal] transaction fees on Opal: %s OPL',
+      helper.util.bigIntToDecimals(balanceOpalAfter - balanceOpalBefore),
+    );
+
+    // commission has not paid in USDT token
+    expect(free == TRANSFER_AMOUNT).to.be.true;
     // ... and parachain native token
     expect(balanceOpalAfter == balanceOpalBefore).to.be.true;
-    console.log(
-      'Opal to Westmint transaction fees on Opal: %s WND',
-      helper.util.bigIntToDecimals(balanceOpalAfter - balanceOpalBefore, WESTMINT_DECIMALS),
-    );    
   });
 
   itSub('Should connect and send USDT from Unique to Statemine back', async ({helper}) => {
@@ -276,9 +276,8 @@
     ];
 
     const feeItem = 1;
-    const destWeight = 500000000000;
 
-    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, destWeight);
+    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');
     
     // the commission has been paid in parachain native token
     balanceOpalFinal = await helper.balance.getSubstrate(alice.address);
@@ -339,9 +338,8 @@
       };
 
       const feeAssetItem = 0;
-      const weightLimit = 5_000_000_000;
 
-      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, weightLimit);
+      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
     });
   
     await helper.wait.newBlocks(3);
@@ -363,20 +361,23 @@
   });
 
   itSub('Should connect and send Relay token back', async ({helper}) => {
+    let relayTokenBalanceBefore: bigint;
+    let relayTokenBalanceAfter: bigint;
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);
+    });
+
     const destination = {
       V1: {
         parents: 1,
-        interior: {X2: [
-          {
-            Parachain: STATEMINE_CHAIN,
-          },
-          {
+        interior: {
+          X1:{
             AccountId32: {
               network: 'Any',
               id: bob.addressRaw,
             },
           },
-        ]},
+        },
       },
     };
 
@@ -390,11 +391,19 @@
     ];
 
     const feeItem = 0;
-    const destWeight = 500000000000;
 
-    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, destWeight);
+    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');
 
     balanceBobFinal = await helper.balance.getSubstrate(bob.address);
-    console.log('Relay (Westend) to Opal transaction fees: %s OPL', balanceBobAfter - balanceBobFinal);
+    console.log('[Opal -> Relay (Westend)] transaction fees: %s OPL',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      await helper.wait.newBlocks(10);
+      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);
+
+      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;
+      console.log('[Opal -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));
+      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;
+    });
   });
 });
modifiedtests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -17,21 +17,430 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {blake2AsHex} from '@polkadot/util-crypto';
 import config from '../config';
-import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util';
+import {XcmV2TraitsError} from '../interfaces';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';
 
 const QUARTZ_CHAIN = 2095;
+const STATEMINE_CHAIN = 1000;
 const KARURA_CHAIN = 2000;
 const MOONRIVER_CHAIN = 2023;
 
+const STATEMINE_PALLET_INSTANCE = 50;
+
 const relayUrl = config.relayUrl;
+const statemineUrl = config.statemineUrl;
 const karuraUrl = config.karuraUrl;
 const moonriverUrl = config.moonriverUrl;
 
+const RELAY_DECIMALS = 12;
+const STATEMINE_DECIMALS = 12;
 const KARURA_DECIMALS = 12;
 
 const TRANSFER_AMOUNT = 2000000000000000000000000n;
 
+const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 
+
+const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
+
+const USDT_ASSET_ID = 100;
+const USDT_ASSET_METADATA_DECIMALS = 18;
+const USDT_ASSET_METADATA_NAME = 'USDT';
+const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
+const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
+const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;
+
+describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  
+  let balanceStmnBefore: bigint;
+  let balanceStmnAfter: bigint;
+
+  let balanceQuartzBefore: bigint;
+  let balanceQuartzAfter: bigint;
+  let balanceQuartzFinal: bigint;
+
+  let balanceBobBefore: bigint;
+  let balanceBobAfter: bigint;
+  let balanceBobFinal: bigint;
+
+  let balanceBobRelayTokenBefore: bigint;
+  let balanceBobRelayTokenAfter: bigint;
+
+
+  before(async () => {
+    await usingPlaygrounds(async (_helper, privateKey) => {
+      alice = await privateKey('//Alice');
+      bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor
+    });
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      // Fund accounts on Statemine(t)
+      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);
+      await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);
+    });
+
+    await usingStateminePlaygrounds(statemineUrl, async (helper) => {
+      const sovereignFundingAmount = 3_500_000_000n; 
+
+      await helper.assets.create(
+        alice,
+        USDT_ASSET_ID,
+        alice.address,
+        USDT_ASSET_METADATA_MINIMAL_BALANCE,
+      );
+      await helper.assets.setMetadata(
+        alice,
+        USDT_ASSET_ID,
+        USDT_ASSET_METADATA_NAME,
+        USDT_ASSET_METADATA_DESCRIPTION,
+        USDT_ASSET_METADATA_DECIMALS,
+      );
+      await helper.assets.mint(
+        alice,
+        USDT_ASSET_ID,
+        alice.address,
+        USDT_ASSET_AMOUNT,
+      );
+
+      // funding parachain sovereing account on Statemine(t).
+      // The sovereign account should be created before any action
+      // (the assets pallet on Statemine(t) check if the sovereign account exists)
+      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);
+      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
+    });
+
+
+    await usingPlaygrounds(async (helper) => {
+      const location = {
+        V1: {
+          parents: 1,
+          interior: {X3: [
+            {
+              Parachain: STATEMINE_CHAIN,
+            },
+            {
+              PalletInstance: STATEMINE_PALLET_INSTANCE,
+            },
+            {
+              GeneralIndex: USDT_ASSET_ID,
+            },
+          ]},
+        },
+      };
+
+      const metadata =
+      {
+        name: USDT_ASSET_ID,
+        symbol: USDT_ASSET_METADATA_NAME,
+        decimals: USDT_ASSET_METADATA_DECIMALS,
+        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,
+      };
+      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);
+    });
+
+
+    // Providing the relay currency to the quartz sender account
+    // (fee for USDT XCM are paid in relay tokens)
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      const destination = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            Parachain: QUARTZ_CHAIN,
+          },
+          },
+        }};
+
+      const beneficiary = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          }},
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: 'Here',
+              },
+            },
+            fun: {
+              Fungible: TRANSFER_AMOUNT_RELAY,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
+    });
+  
+  });
+
+  itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {
+    await usingStateminePlaygrounds(statemineUrl, async (helper) => {
+      const dest = {
+        V1: {
+          parents: 1,
+          interior: {X1: {
+            Parachain: QUARTZ_CHAIN,
+          },
+          },
+        }};
+
+      const beneficiary = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          }},
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: {
+                  X2: [
+                    {
+                      PalletInstance: STATEMINE_PALLET_INSTANCE,
+                    },
+                    {
+                      GeneralIndex: USDT_ASSET_ID,
+                    }, 
+                  ]},
+              },
+            },
+            fun: {
+              Fungible: TRANSFER_AMOUNT,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);
+      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');
+
+      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);
+
+      // common good parachain take commission in it native token
+      console.log(
+        '[Statemine -> Quartz] transaction fees on Statemine: %s WND',
+        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),
+      );
+      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
+
+    });
+
+
+    // ensure that asset has been delivered
+    await helper.wait.newBlocks(3);
+
+    // expext collection id will be with id 1
+    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+
+    balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);
+
+    console.log(
+      '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',
+      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
+    );
+    console.log(
+      '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',
+      helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),
+    );    
+    // commission has not paid in USDT token
+    expect(free).to.be.equal(TRANSFER_AMOUNT);
+    // ... and parachain native token
+    expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;
+  });
+
+  itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {
+    const destination = {
+      V1: {
+        parents: 1,
+        interior: {X2: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          },
+        ]},
+      },
+    };
+
+    const relayFee = 400_000_000_000_000n;
+    const currencies: [any, bigint][] = [
+      [
+        {
+          ForeignAssetId: 0,
+        },
+        TRANSFER_AMOUNT,
+      ], 
+      [
+        {
+          NativeAssetId: 'Parent',
+        },
+        relayFee,
+      ],
+    ];
+
+    const feeItem = 1;
+
+    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');
+    
+    // the commission has been paid in parachain native token
+    balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);
+    console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter));
+    expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;
+
+    await usingStateminePlaygrounds(statemineUrl, async (helper) => {
+      await helper.wait.newBlocks(3);
+      
+      // The USDT token never paid fees. Its amount not changed from begin value.
+      // Also check that xcm transfer has been succeeded 
+      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;
+    });
+  });
+
+  itSub('Should connect and send Relay token to Quartz', async ({helper}) => {
+    balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      const destination = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            Parachain: QUARTZ_CHAIN,
+          },
+          },
+        }};
+
+      const beneficiary = {
+        V1: {
+          parents: 0,
+          interior: {X1: {
+            AccountId32: {
+              network: 'Any',
+              id: bob.addressRaw,
+            },
+          }},
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: 'Here',
+              },
+            },
+            fun: {
+              Fungible: TRANSFER_AMOUNT_RELAY,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
+    });
+  
+    await helper.wait.newBlocks(3);
+
+    balanceBobAfter = await helper.balance.getSubstrate(bob.address);  
+    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+
+    const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
+    const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
+    console.log(
+      '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',
+      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),
+    );
+    console.log(
+      '[Relay (Westend) -> Quartz] transaction fees: %s WND',
+      helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),
+    );
+    console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);
+    expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;
+    expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;
+  });
+
+  itSub('Should connect and send Relay token back', async ({helper}) => {
+    let relayTokenBalanceBefore: bigint;
+    let relayTokenBalanceAfter: bigint;
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);
+    });
+
+    const destination = {
+      V1: {
+        parents: 1,
+        interior: {
+          X1:{
+            AccountId32: {
+              network: 'Any',
+              id: bob.addressRaw,
+            },
+          },
+        },
+      },
+    };
+
+    const currencies: any = [
+      [
+        {
+          NativeAssetId: 'Parent',
+        },
+        TRANSFER_AMOUNT_RELAY,
+      ],
+    ];
+
+    const feeItem = 0;
+
+    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');
+
+    balanceBobFinal = await helper.balance.getSubstrate(bob.address);
+    console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));
+
+    await usingRelayPlaygrounds(relayUrl, async (helper) => {
+      await helper.wait.newBlocks(10);
+      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);
+
+      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;
+      console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));
+      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;
+    });
+  });
+});
+
 describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {
   let alice: IKeyringPair;
   let randomAccount: IKeyringPair;
@@ -123,14 +532,13 @@
     };
 
     const feeAssetItem = 0;
-    const weightLimit = 5000000000;
 
-    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);
+    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
     balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
 
     const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
+    expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;
     console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));
-    expect(qtzFees > 0n).to.be.true;
 
     await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
       await helper.wait.newBlocks(3);
@@ -173,9 +581,7 @@
         ForeignAsset: 0,
       };
 
-      const destWeight = 50000000;
-
-      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);
+      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, 'Unlimited');
       balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
       balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
 
@@ -188,7 +594,7 @@
       );
       console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));
 
-      expect(karFees > 0).to.be.true;
+      expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;
       expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
     });
 
@@ -215,76 +621,7 @@
       alice = await privateKey('//Alice');
     });
   });
-
-  itSub('Quartz rejects tokens from the Relay', async ({helper}) => {
-    await usingRelayPlaygrounds(relayUrl, async (helper) => {
-      const destination = {
-        V1: {
-          parents: 0,
-          interior: {X1: {
-            Parachain: QUARTZ_CHAIN,
-          },
-          },
-        }};
-
-      const beneficiary = {
-        V1: {
-          parents: 0,
-          interior: {X1: {
-            AccountId32: {
-              network: 'Any',
-              id: alice.addressRaw,
-            },
-          }},
-        },
-      };
-
-      const assets = {
-        V1: [
-          {
-            id: {
-              Concrete: {
-                parents: 0,
-                interior: 'Here',
-              },
-            },
-            fun: {
-              Fungible: 50_000_000_000_000_000n,
-            },
-          },
-        ],
-      };
-
-      const feeAssetItem = 0;
-      const weightLimit = 5_000_000_000;
-
-      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);
-    });
 
-    const maxWaitBlocks = 3;
-
-    const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');
-
-    expect(
-      dmpQueueExecutedDownward != null,
-      '[Relay] dmpQueue.ExecutedDownward event is expected',
-    ).to.be.true;
-
-    const event = dmpQueueExecutedDownward!.event;
-    const outcome = event.data[1] as XcmV2TraitsOutcome;
-
-    expect(
-      outcome.isIncomplete,
-      '[Relay] The outcome of the XCM should be `Incomplete`',
-    ).to.be.true;
-
-    const incomplete = outcome.asIncomplete;
-    expect(
-      incomplete[1].toString() == 'AssetNotFound',
-      '[Relay] The XCM error should be `AssetNotFound`',
-    ).to.be.true;
-  });
-
   itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {
     await usingKaruraPlaygrounds(karuraUrl, async (helper) => {
       const destination = {
@@ -308,9 +645,7 @@
         Token: 'KAR',
       };
 
-      const destWeight = 50000000;
-
-      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);
+      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');
     });
 
     const maxWaitBlocks = 3;
@@ -326,8 +661,8 @@
     const outcome = event.data[1] as XcmV2TraitsError;
 
     expect(
-      outcome.isUntrustedReserveLocation,
-      '[Karura] The XCM error should be `UntrustedReserveLocation`',
+      outcome.isFailedToTransactAsset,
+      '[Karura] The XCM error should be `FailedToTransactAsset`',
     ).to.be.true;
   });
 });
@@ -420,7 +755,7 @@
 
       // >>> Propose external motion through council >>>
       console.log('Propose external motion through council.......');
-      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);
+      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});
       const encodedMotion = externalMotion?.method.toHex() || '';
       const motionHash = blake2AsHex(encodedMotion);
       console.log('Motion hash is %s', motionHash);
@@ -431,7 +766,16 @@
       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, 1_000_000_000, externalMotion.encodedLength);
+      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 <<<
 
@@ -448,7 +792,16 @@
       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, 1_000_000_000, fastTrack.encodedLength);
+      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 <<<
 
@@ -504,16 +857,15 @@
       },
     };
     const amount = TRANSFER_AMOUNT;
-    const destWeight = 850000000;
 
-    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);
+    await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');
 
     balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);
     expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;
 
     const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;
     console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));
-    expect(transactionFees > 0).to.be.true;
+    expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;
 
     await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
       await helper.wait.newBlocks(3);
@@ -559,15 +911,14 @@
           },
         },
       };
-      const destWeight = 50000000;
 
-      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);
+      await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');
 
       balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);
 
       const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;
       console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));
-      expect(movrFees > 0).to.be.true;
+      expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;
 
       const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);
 
modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
before · tests/src/xcm/xcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds} from '../util';2223const UNIQUE_CHAIN = 2037;24const ACALA_CHAIN = 2000;25const MOONBEAM_CHAIN = 2004;2627const relayUrl = config.relayUrl;28const acalaUrl = config.acalaUrl;29const moonbeamUrl = config.moonbeamUrl;3031const ACALA_DECIMALS = 12;3233const TRANSFER_AMOUNT = 2000000000000000000000000n;3435describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {36  let alice: IKeyringPair;37  let randomAccount: IKeyringPair;3839  let balanceUniqueTokenInit: bigint;40  let balanceUniqueTokenMiddle: bigint;41  let balanceUniqueTokenFinal: bigint;42  let balanceAcalaTokenInit: bigint;43  let balanceAcalaTokenMiddle: bigint;44  let balanceAcalaTokenFinal: bigint;45  let balanceUniqueForeignTokenInit: bigint;46  let balanceUniqueForeignTokenMiddle: bigint;47  let balanceUniqueForeignTokenFinal: bigint;4849  before(async () => {50    await usingPlaygrounds(async (helper, privateKey) => {51      alice = await privateKey('//Alice');52      [randomAccount] = await helper.arrange.createAccounts([0n], alice);53    });5455    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {56      const destination = {57        V0: {58          X2: [59            'Parent',60            {61              Parachain: UNIQUE_CHAIN,62            },63          ],64        },65      };6667      const metadata = {68        name: 'UNQ',69        symbol: 'UNQ',70        decimals: 18,71        minimalBalance: 1n,72      };7374      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);75      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);76      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);77      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});78    });7980    await usingPlaygrounds(async (helper) => {81      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);82      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);83    });84  });8586  itSub('Should connect and send UNQ to Acala', async ({helper}) => {8788    const destination = {89      V0: {90        X2: [91          'Parent',92          {93            Parachain: ACALA_CHAIN,94          },95        ],96      },97    };9899    const beneficiary = {100      V0: {101        X1: {102          AccountId32: {103            network: 'Any',104            id: randomAccount.addressRaw,105          },106        },107      },108    };109110    const assets = {111      V1: [112        {113          id: {114            Concrete: {115              parents: 0,116              interior: 'Here',117            },118          },119          fun: {120            Fungible: TRANSFER_AMOUNT,121          },122        },123      ],124    };125126    const feeAssetItem = 0;127    const weightLimit = 5000000000;128129    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);130131    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);132133    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;134    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));135    expect(unqFees > 0n).to.be.true;136137    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {138      await helper.wait.newBlocks(3);139140      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});141      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);142143      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;144      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;145146      console.log(147        '[Unique -> Acala] transaction fees on Acala: %s ACA',148        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),149      );150      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));151      expect(acaFees == 0n).to.be.true;152      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;153    });154  });155156  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {157    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {158      const destination = {159        V1: {160          parents: 1,161          interior: {162            X2: [163              {Parachain: UNIQUE_CHAIN},164              {165                AccountId32: {166                  network: 'Any',167                  id: randomAccount.addressRaw,168                },169              },170            ],171          },172        },173      };174175      const id = {176        ForeignAsset: 0,177      };178179      const destWeight = 50000000;180181      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);182183      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);184      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);185186      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;187      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;188189      console.log(190        '[Acala -> Unique] transaction fees on Acala: %s ACA',191        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),192      );193      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));194195      expect(acaFees > 0).to.be.true;196      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;197    });198199    await helper.wait.newBlocks(3);200201    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);202    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;203    expect(actuallyDelivered > 0).to.be.true;204205    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));206207    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;208    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));209    expect(unqFees == 0n).to.be.true;210  });211});212213// These tests are relevant only when the foreign asset pallet is disabled214describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {215  let alice: IKeyringPair;216217  before(async () => {218    await usingPlaygrounds(async (_helper, privateKey) => {219      alice = await privateKey('//Alice');220    });221  });222223  itSub('Unique rejects tokens from the Relay', async ({helper}) => {224    await usingRelayPlaygrounds(relayUrl, async (helper) => {225      const destination = {226        V1: {227          parents: 0,228          interior: {X1: {229            Parachain: UNIQUE_CHAIN,230          },231          },232        }};233234      const beneficiary = {235        V1: {236          parents: 0,237          interior: {X1: {238            AccountId32: {239              network: 'Any',240              id: alice.addressRaw,241            },242          }},243        },244      };245246      const assets = {247        V1: [248          {249            id: {250              Concrete: {251                parents: 0,252                interior: 'Here',253              },254            },255            fun: {256              Fungible: 50_000_000_000_000_000n,257            },258          },259        ],260      };261262      const feeAssetItem = 0;263      const weightLimit = 5_000_000_000;264265      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);266    });267268    const maxWaitBlocks = 3;269270    const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');271272    expect(273      dmpQueueExecutedDownward != null,274      '[Relay] dmpQueue.ExecutedDownward event is expected',275    ).to.be.true;276277    const event = dmpQueueExecutedDownward!.event;278    const outcome = event.data[1] as XcmV2TraitsOutcome;279280    expect(281      outcome.isIncomplete,282      '[Relay] The outcome of the XCM should be `Incomplete`',283    ).to.be.true;284285    const incomplete = outcome.asIncomplete;286    expect(287      incomplete[1].toString() == 'AssetNotFound',288      '[Relay] The XCM error should be `AssetNotFound`',289    ).to.be.true;290  });291292  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {293    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {294      const destination = {295        V1: {296          parents: 1,297          interior: {298            X2: [299              {Parachain: UNIQUE_CHAIN},300              {301                AccountId32: {302                  network: 'Any',303                  id: alice.addressRaw,304                },305              },306            ],307          },308        },309      };310311      const id = {312        Token: 'ACA',313      };314315      const destWeight = 50000000;316317      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);318    });319320    const maxWaitBlocks = 3;321322    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');323324    expect(325      xcmpQueueFailEvent != null,326      '[Acala] xcmpQueue.FailEvent event is expected',327    ).to.be.true;328329    const event = xcmpQueueFailEvent!.event;330    const outcome = event.data[1] as XcmV2TraitsError;331332    expect(333      outcome.isUntrustedReserveLocation,334      '[Acala] The XCM error should be `UntrustedReserveLocation`',335    ).to.be.true;336  });337});338339describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {340  // Unique constants341  let uniqueDonor: IKeyringPair;342  let uniqueAssetLocation;343344  let randomAccountUnique: IKeyringPair;345  let randomAccountMoonbeam: IKeyringPair;346347  // Moonbeam constants348  let assetId: string;349350  const councilVotingThreshold = 2;351  const technicalCommitteeThreshold = 2;352  const votingPeriod = 3;353  const delayPeriod = 0;354355  const uniqueAssetMetadata = {356    name: 'xcUnique',357    symbol: 'xcUNQ',358    decimals: 18,359    isFrozen: false,360    minimalBalance: 1n,361  };362363  let balanceUniqueTokenInit: bigint;364  let balanceUniqueTokenMiddle: bigint;365  let balanceUniqueTokenFinal: bigint;366  let balanceForeignUnqTokenInit: bigint;367  let balanceForeignUnqTokenMiddle: bigint;368  let balanceForeignUnqTokenFinal: bigint;369  let balanceGlmrTokenInit: bigint;370  let balanceGlmrTokenMiddle: bigint;371  let balanceGlmrTokenFinal: bigint;372373  before(async () => {374    await usingPlaygrounds(async (helper, privateKey) => {375      uniqueDonor = await privateKey('//Alice');376      [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);377378      balanceForeignUnqTokenInit = 0n;379    });380381    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {382      const alithAccount = helper.account.alithAccount();383      const baltatharAccount = helper.account.baltatharAccount();384      const dorothyAccount = helper.account.dorothyAccount();385386      randomAccountMoonbeam = helper.account.create();387388      // >>> Sponsoring Dorothy >>>389      console.log('Sponsoring Dorothy.......');390      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);391      console.log('Sponsoring Dorothy.......DONE');392      // <<< Sponsoring Dorothy <<<393394      uniqueAssetLocation = {395        XCM: {396          parents: 1,397          interior: {X1: {Parachain: UNIQUE_CHAIN}},398        },399      };400      const existentialDeposit = 1n;401      const isSufficient = true;402      const unitsPerSecond = 1n;403      const numAssetsWeightHint = 0;404405      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({406        location: uniqueAssetLocation,407        metadata: uniqueAssetMetadata,408        existentialDeposit,409        isSufficient,410        unitsPerSecond,411        numAssetsWeightHint,412      });413      const proposalHash = blake2AsHex(encodedProposal);414415      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);416      console.log('Encoded length %d', encodedProposal.length);417      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);418419      // >>> Note motion preimage >>>420      console.log('Note motion preimage.......');421      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);422      console.log('Note motion preimage.......DONE');423      // <<< Note motion preimage <<<424425      // >>> Propose external motion through council >>>426      console.log('Propose external motion through council.......');427      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);428      const encodedMotion = externalMotion?.method.toHex() || '';429      const motionHash = blake2AsHex(encodedMotion);430      console.log('Motion hash is %s', motionHash);431432      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);433434      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;435      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);436      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);437438      await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);439      console.log('Propose external motion through council.......DONE');440      // <<< Propose external motion through council <<<441442      // >>> Fast track proposal through technical committee >>>443      console.log('Fast track proposal through technical committee.......');444      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);445      const encodedFastTrack = fastTrack?.method.toHex() || '';446      const fastTrackHash = blake2AsHex(encodedFastTrack);447      console.log('FastTrack hash is %s', fastTrackHash);448449      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);450451      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;452      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);453      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);454455      await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);456      console.log('Fast track proposal through technical committee.......DONE');457      // <<< Fast track proposal through technical committee <<<458459      // >>> Referendum voting >>>460      console.log('Referendum voting.......');461      await helper.democracy.referendumVote(dorothyAccount, 0, {462        balance: 10_000_000_000_000_000_000n,463        vote: {aye: true, conviction: 1},464      });465      console.log('Referendum voting.......DONE');466      // <<< Referendum voting <<<467468      // >>> Acquire Unique AssetId Info on Moonbeam >>>469      console.log('Acquire Unique AssetId Info on Moonbeam.......');470471      // Wait for the democracy execute472      await helper.wait.newBlocks(5);473474      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();475476      console.log('UNQ asset ID is %s', assetId);477      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');478      // >>> Acquire Unique AssetId Info on Moonbeam >>>479480      // >>> Sponsoring random Account >>>481      console.log('Sponsoring random Account.......');482      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);483      console.log('Sponsoring random Account.......DONE');484      // <<< Sponsoring random Account <<<485486      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);487    });488489    await usingPlaygrounds(async (helper) => {490      await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);491      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);492    });493  });494495  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {496    const currencyId = {497      NativeAssetId: 'Here',498    };499    const dest = {500      V1: {501        parents: 1,502        interior: {503          X2: [504            {Parachain: MOONBEAM_CHAIN},505            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},506          ],507        },508      },509    };510    const amount = TRANSFER_AMOUNT;511    const destWeight = 850000000;512513    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, destWeight);514515    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);516    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;517518    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;519    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));520    expect(transactionFees > 0).to.be.true;521522    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {523      await helper.wait.newBlocks(3);524525      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);526527      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;528      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));529      expect(glmrFees == 0n).to.be.true;530531      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;532533      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;534      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));535      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;536    });537  });538539  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {540    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {541      const asset = {542        V1: {543          id: {544            Concrete: {545              parents: 1,546              interior: {547                X1: {Parachain: UNIQUE_CHAIN},548              },549            },550          },551          fun: {552            Fungible: TRANSFER_AMOUNT,553          },554        },555      };556      const destination = {557        V1: {558          parents: 1,559          interior: {560            X2: [561              {Parachain: UNIQUE_CHAIN},562              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},563            ],564          },565        },566      };567      const destWeight = 50000000;568569      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, destWeight);570571      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);572573      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;574      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));575      expect(glmrFees > 0).to.be.true;576577      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);578579      expect(unqRandomAccountAsset).to.be.null;580      581      balanceForeignUnqTokenFinal = 0n;582583      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;584      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));585      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;586    });587588    await helper.wait.newBlocks(3);589590    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);591    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;592    expect(actuallyDelivered > 0).to.be.true;593594    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));595596    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;597    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));598    expect(unqFees == 0n).to.be.true;599  });600});
after · tests/src/xcm/xcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds} from '../util';2223const UNIQUE_CHAIN = 2037;24const STATEMINT_CHAIN = 1000;25const ACALA_CHAIN = 2000;26const MOONBEAM_CHAIN = 2004;2728const STATEMINT_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemintUrl = config.statemintUrl;32const acalaUrl = config.acalaUrl;33const moonbeamUrl = config.moonbeamUrl;3435const RELAY_DECIMALS = 12;36const STATEMINT_DECIMALS = 12;37const ACALA_DECIMALS = 12;3839const TRANSFER_AMOUNT = 2000000000000000000000000n;4041const FUNDING_AMOUNT = 3_500_000_0000_000_000n; 4243const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4445const USDT_ASSET_ID = 100;46const USDT_ASSET_METADATA_DECIMALS = 18;47const USDT_ASSET_METADATA_NAME = 'USDT';48const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';49const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;50const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5152describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {53  let alice: IKeyringPair;54  let bob: IKeyringPair;55  56  let balanceStmnBefore: bigint;57  let balanceStmnAfter: bigint;5859  let balanceUniqueBefore: bigint;60  let balanceUniqueAfter: bigint;61  let balanceUniqueFinal: bigint;6263  let balanceBobBefore: bigint;64  let balanceBobAfter: bigint;65  let balanceBobFinal: bigint;6667  let balanceBobRelayTokenBefore: bigint;68  let balanceBobRelayTokenAfter: bigint;697071  before(async () => {72    await usingPlaygrounds(async (_helper, privateKey) => {73      alice = await privateKey('//Alice');74      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor75    });7677    await usingRelayPlaygrounds(relayUrl, async (helper) => {78      // Fund accounts on Statemint79      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);80      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);81    });8283    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {84      const sovereignFundingAmount = 3_500_000_000n; 8586      await helper.assets.create(87        alice,88        USDT_ASSET_ID,89        alice.address,90        USDT_ASSET_METADATA_MINIMAL_BALANCE,91      );92      await helper.assets.setMetadata(93        alice,94        USDT_ASSET_ID,95        USDT_ASSET_METADATA_NAME,96        USDT_ASSET_METADATA_DESCRIPTION,97        USDT_ASSET_METADATA_DECIMALS,98      );99      await helper.assets.mint(100        alice,101        USDT_ASSET_ID,102        alice.address,103        USDT_ASSET_AMOUNT,104      );105106      // funding parachain sovereing account on Statemint.107      // The sovereign account should be created before any action108      // (the assets pallet on Statemint check if the sovereign account exists)109      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);110      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);111    });112113114    await usingPlaygrounds(async (helper) => {115      const location = {116        V1: {117          parents: 1,118          interior: {X3: [119            {120              Parachain: STATEMINT_CHAIN,121            },122            {123              PalletInstance: STATEMINT_PALLET_INSTANCE,124            },125            {126              GeneralIndex: USDT_ASSET_ID,127            },128          ]},129        },130      };131132      const metadata =133      {134        name: USDT_ASSET_ID,135        symbol: USDT_ASSET_METADATA_NAME,136        decimals: USDT_ASSET_METADATA_DECIMALS,137        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,138      };139      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);140      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);141    });142143144    // Providing the relay currency to the unique sender account145    // (fee for USDT XCM are paid in relay tokens)146    await usingRelayPlaygrounds(relayUrl, async (helper) => {147      const destination = {148        V1: {149          parents: 0,150          interior: {X1: {151            Parachain: UNIQUE_CHAIN,152          },153          },154        }};155156      const beneficiary = {157        V1: {158          parents: 0,159          interior: {X1: {160            AccountId32: {161              network: 'Any',162              id: alice.addressRaw,163            },164          }},165        },166      };167168      const assets = {169        V1: [170          {171            id: {172              Concrete: {173                parents: 0,174                interior: 'Here',175              },176            },177            fun: {178              Fungible: TRANSFER_AMOUNT_RELAY,179            },180          },181        ],182      };183184      const feeAssetItem = 0;185186      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');187    });188  189  });190191  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {192    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {193      const dest = {194        V1: {195          parents: 1,196          interior: {X1: {197            Parachain: UNIQUE_CHAIN,198          },199          },200        }};201202      const beneficiary = {203        V1: {204          parents: 0,205          interior: {X1: {206            AccountId32: {207              network: 'Any',208              id: alice.addressRaw,209            },210          }},211        },212      };213214      const assets = {215        V1: [216          {217            id: {218              Concrete: {219                parents: 0,220                interior: {221                  X2: [222                    {223                      PalletInstance: STATEMINT_PALLET_INSTANCE,224                    },225                    {226                      GeneralIndex: USDT_ASSET_ID,227                    }, 228                  ]},229              },230            },231            fun: {232              Fungible: TRANSFER_AMOUNT,233            },234          },235        ],236      };237238      const feeAssetItem = 0;239240      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);241      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');242243      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);244245      // common good parachain take commission in it native token246      console.log(247        '[Statemint -> Unique] transaction fees on Statemint: %s WND',248        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),249      );250      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;251252    });253254255    // ensure that asset has been delivered256    await helper.wait.newBlocks(3);257258    // expext collection id will be with id 1259    const free = await helper.ft.getBalance(1, {Substrate: alice.address});260261    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);262263    console.log(264      '[Statemint -> Unique] transaction fees on Unique: %s USDT',265      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),266    );267    console.log(268      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',269      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),270    );    271    // commission has not paid in USDT token272    expect(free).to.be.equal(TRANSFER_AMOUNT);273    // ... and parachain native token274    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;275  });276277  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {278    const destination = {279      V1: {280        parents: 1,281        interior: {X2: [282          {283            Parachain: STATEMINT_CHAIN,284          },285          {286            AccountId32: {287              network: 'Any',288              id: alice.addressRaw,289            },290          },291        ]},292      },293    };294295    const relayFee = 400_000_000_000_000n;296    const currencies: [any, bigint][] = [297      [298        {299          ForeignAssetId: 0,300        },301        TRANSFER_AMOUNT,302      ], 303      [304        {305          NativeAssetId: 'Parent',306        },307        relayFee,308      ],309    ];310311    const feeItem = 1;312313    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');314    315    // the commission has been paid in parachain native token316    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);317    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueFinal - balanceUniqueAfter));318    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;319320    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {321      await helper.wait.newBlocks(3);322      323      // The USDT token never paid fees. Its amount not changed from begin value.324      // Also check that xcm transfer has been succeeded 325      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;326    });327  });328329  itSub('Should connect and send Relay token to Unique', async ({helper}) => {330    balanceBobBefore = await helper.balance.getSubstrate(bob.address);331    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});332333    await usingRelayPlaygrounds(relayUrl, async (helper) => {334      const destination = {335        V1: {336          parents: 0,337          interior: {X1: {338            Parachain: UNIQUE_CHAIN,339          },340          },341        }};342343      const beneficiary = {344        V1: {345          parents: 0,346          interior: {X1: {347            AccountId32: {348              network: 'Any',349              id: bob.addressRaw,350            },351          }},352        },353      };354355      const assets = {356        V1: [357          {358            id: {359              Concrete: {360                parents: 0,361                interior: 'Here',362              },363            },364            fun: {365              Fungible: TRANSFER_AMOUNT_RELAY,366            },367          },368        ],369      };370371      const feeAssetItem = 0;372373      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');374    });375  376    await helper.wait.newBlocks(3);377378    balanceBobAfter = await helper.balance.getSubstrate(bob.address);  379    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});380381    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;382    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;383    console.log(384      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',385      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),386    );387    console.log(388      '[Relay (Westend) -> Unique] transaction fees: %s WND',389      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),390    );391    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);392    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;393    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;394  });395396  itSub('Should connect and send Relay token back', async ({helper}) => {397    let relayTokenBalanceBefore: bigint;398    let relayTokenBalanceAfter: bigint;399    await usingRelayPlaygrounds(relayUrl, async (helper) => {400      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);401    });402403    const destination = {404      V1: {405        parents: 1,406        interior: {407          X1:{408            AccountId32: {409              network: 'Any',410              id: bob.addressRaw,411            },412          },413        },414      },415    };416417    const currencies: any = [418      [419        {420          NativeAssetId: 'Parent',421        },422        TRANSFER_AMOUNT_RELAY,423      ],424    ];425426    const feeItem = 0;427428    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');429430    balanceBobFinal = await helper.balance.getSubstrate(bob.address);431    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));432433    await usingRelayPlaygrounds(relayUrl, async (helper) => {434      await helper.wait.newBlocks(10);435      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);436437      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;438      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));439      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;440    });441  });442});443444describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {445  let alice: IKeyringPair;446  let randomAccount: IKeyringPair;447448  let balanceUniqueTokenInit: bigint;449  let balanceUniqueTokenMiddle: bigint;450  let balanceUniqueTokenFinal: bigint;451  let balanceAcalaTokenInit: bigint;452  let balanceAcalaTokenMiddle: bigint;453  let balanceAcalaTokenFinal: bigint;454  let balanceUniqueForeignTokenInit: bigint;455  let balanceUniqueForeignTokenMiddle: bigint;456  let balanceUniqueForeignTokenFinal: bigint;457458  before(async () => {459    await usingPlaygrounds(async (helper, privateKey) => {460      alice = await privateKey('//Alice');461      [randomAccount] = await helper.arrange.createAccounts([0n], alice);462    });463464    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {465      const destination = {466        V0: {467          X2: [468            'Parent',469            {470              Parachain: UNIQUE_CHAIN,471            },472          ],473        },474      };475476      const metadata = {477        name: 'UNQ',478        symbol: 'UNQ',479        decimals: 18,480        minimalBalance: 1n,481      };482483      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);484      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);485      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);486      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});487    });488489    await usingPlaygrounds(async (helper) => {490      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);491      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);492    });493  });494495  itSub('Should connect and send UNQ to Acala', async ({helper}) => {496497    const destination = {498      V0: {499        X2: [500          'Parent',501          {502            Parachain: ACALA_CHAIN,503          },504        ],505      },506    };507508    const beneficiary = {509      V0: {510        X1: {511          AccountId32: {512            network: 'Any',513            id: randomAccount.addressRaw,514          },515        },516      },517    };518519    const assets = {520      V1: [521        {522          id: {523            Concrete: {524              parents: 0,525              interior: 'Here',526            },527          },528          fun: {529            Fungible: TRANSFER_AMOUNT,530          },531        },532      ],533    };534535    const feeAssetItem = 0;536537    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');538    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);539540    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;541    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));542    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;543544    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {545      await helper.wait.newBlocks(3);546547      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});548      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);549550      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;551      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;552553      console.log(554        '[Unique -> Acala] transaction fees on Acala: %s ACA',555        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),556      );557      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));558      expect(acaFees == 0n).to.be.true;559      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;560    });561  });562563  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {564    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {565      const destination = {566        V1: {567          parents: 1,568          interior: {569            X2: [570              {Parachain: UNIQUE_CHAIN},571              {572                AccountId32: {573                  network: 'Any',574                  id: randomAccount.addressRaw,575                },576              },577            ],578          },579        },580      };581582      const id = {583        ForeignAsset: 0,584      };585586      await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, 'Unlimited');587      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);588      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);589590      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;591      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;592593      console.log(594        '[Acala -> Unique] transaction fees on Acala: %s ACA',595        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),596      );597      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));598599      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;600      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;601    });602603    await helper.wait.newBlocks(3);604605    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);606    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;607    expect(actuallyDelivered > 0).to.be.true;608609    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));610611    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;612    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));613    expect(unqFees == 0n).to.be.true;614  });615});616617// These tests are relevant only when the foreign asset pallet is disabled618describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {619  let alice: IKeyringPair;620621  before(async () => {622    await usingPlaygrounds(async (_helper, privateKey) => {623      alice = await privateKey('//Alice');624    });625  });626627  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {628    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {629      const destination = {630        V1: {631          parents: 1,632          interior: {633            X2: [634              {Parachain: UNIQUE_CHAIN},635              {636                AccountId32: {637                  network: 'Any',638                  id: alice.addressRaw,639                },640              },641            ],642          },643        },644      };645646      const id = {647        Token: 'ACA',648      };649650      await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');651    });652653    const maxWaitBlocks = 3;654655    const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');656657    expect(658      xcmpQueueFailEvent != null,659      '[Acala] xcmpQueue.FailEvent event is expected',660    ).to.be.true;661662    const event = xcmpQueueFailEvent!.event;663    const outcome = event.data[1] as XcmV2TraitsError;664665    expect(666      outcome.isFailedToTransactAsset,667      '[Acala] The XCM error should be `FailedToTransactAsset`',668    ).to.be.true;669  });670});671672describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {673  // Unique constants674  let uniqueDonor: IKeyringPair;675  let uniqueAssetLocation;676677  let randomAccountUnique: IKeyringPair;678  let randomAccountMoonbeam: IKeyringPair;679680  // Moonbeam constants681  let assetId: string;682683  const councilVotingThreshold = 2;684  const technicalCommitteeThreshold = 2;685  const votingPeriod = 3;686  const delayPeriod = 0;687688  const uniqueAssetMetadata = {689    name: 'xcUnique',690    symbol: 'xcUNQ',691    decimals: 18,692    isFrozen: false,693    minimalBalance: 1n,694  };695696  let balanceUniqueTokenInit: bigint;697  let balanceUniqueTokenMiddle: bigint;698  let balanceUniqueTokenFinal: bigint;699  let balanceForeignUnqTokenInit: bigint;700  let balanceForeignUnqTokenMiddle: bigint;701  let balanceForeignUnqTokenFinal: bigint;702  let balanceGlmrTokenInit: bigint;703  let balanceGlmrTokenMiddle: bigint;704  let balanceGlmrTokenFinal: bigint;705706  before(async () => {707    await usingPlaygrounds(async (helper, privateKey) => {708      uniqueDonor = await privateKey('//Alice');709      [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);710711      balanceForeignUnqTokenInit = 0n;712    });713714    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {715      const alithAccount = helper.account.alithAccount();716      const baltatharAccount = helper.account.baltatharAccount();717      const dorothyAccount = helper.account.dorothyAccount();718719      randomAccountMoonbeam = helper.account.create();720721      // >>> Sponsoring Dorothy >>>722      console.log('Sponsoring Dorothy.......');723      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);724      console.log('Sponsoring Dorothy.......DONE');725      // <<< Sponsoring Dorothy <<<726727      uniqueAssetLocation = {728        XCM: {729          parents: 1,730          interior: {X1: {Parachain: UNIQUE_CHAIN}},731        },732      };733      const existentialDeposit = 1n;734      const isSufficient = true;735      const unitsPerSecond = 1n;736      const numAssetsWeightHint = 0;737738      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({739        location: uniqueAssetLocation,740        metadata: uniqueAssetMetadata,741        existentialDeposit,742        isSufficient,743        unitsPerSecond,744        numAssetsWeightHint,745      });746      const proposalHash = blake2AsHex(encodedProposal);747748      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);749      console.log('Encoded length %d', encodedProposal.length);750      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);751752      // >>> Note motion preimage >>>753      console.log('Note motion preimage.......');754      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);755      console.log('Note motion preimage.......DONE');756      // <<< Note motion preimage <<<757758      // >>> Propose external motion through council >>>759      console.log('Propose external motion through council.......');760      const externalMotion = helper.democracy.externalProposeMajority(proposalHash);761      const encodedMotion = externalMotion?.method.toHex() || '';762      const motionHash = blake2AsHex(encodedMotion);763      console.log('Motion hash is %s', motionHash);764765      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);766767      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;768      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);769      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);770771      await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);772      console.log('Propose external motion through council.......DONE');773      // <<< Propose external motion through council <<<774775      // >>> Fast track proposal through technical committee >>>776      console.log('Fast track proposal through technical committee.......');777      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);778      const encodedFastTrack = fastTrack?.method.toHex() || '';779      const fastTrackHash = blake2AsHex(encodedFastTrack);780      console.log('FastTrack hash is %s', fastTrackHash);781782      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);783784      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;785      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);786      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);787788      await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);789      console.log('Fast track proposal through technical committee.......DONE');790      // <<< Fast track proposal through technical committee <<<791792      // >>> Referendum voting >>>793      console.log('Referendum voting.......');794      await helper.democracy.referendumVote(dorothyAccount, 0, {795        balance: 10_000_000_000_000_000_000n,796        vote: {aye: true, conviction: 1},797      });798      console.log('Referendum voting.......DONE');799      // <<< Referendum voting <<<800801      // >>> Acquire Unique AssetId Info on Moonbeam >>>802      console.log('Acquire Unique AssetId Info on Moonbeam.......');803804      // Wait for the democracy execute805      await helper.wait.newBlocks(5);806807      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();808809      console.log('UNQ asset ID is %s', assetId);810      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');811      // >>> Acquire Unique AssetId Info on Moonbeam >>>812813      // >>> Sponsoring random Account >>>814      console.log('Sponsoring random Account.......');815      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);816      console.log('Sponsoring random Account.......DONE');817      // <<< Sponsoring random Account <<<818819      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);820    });821822    await usingPlaygrounds(async (helper) => {823      await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);824      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);825    });826  });827828  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {829    const currencyId = {830      NativeAssetId: 'Here',831    };832    const dest = {833      V1: {834        parents: 1,835        interior: {836          X2: [837            {Parachain: MOONBEAM_CHAIN},838            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},839          ],840        },841      },842    };843    const amount = TRANSFER_AMOUNT;844845    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');846847    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);848    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;849850    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;851    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));852    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;853854    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {855      await helper.wait.newBlocks(3);856857      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);858859      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;860      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));861      expect(glmrFees == 0n).to.be.true;862863      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;864865      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;866      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));867      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;868    });869  });870871  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {872    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {873      const asset = {874        V1: {875          id: {876            Concrete: {877              parents: 1,878              interior: {879                X1: {Parachain: UNIQUE_CHAIN},880              },881            },882          },883          fun: {884            Fungible: TRANSFER_AMOUNT,885          },886        },887      };888      const destination = {889        V1: {890          parents: 1,891          interior: {892            X2: [893              {Parachain: UNIQUE_CHAIN},894              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},895            ],896          },897        },898      };899      const destWeight = 50000000;900901      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, destWeight);902903      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);904905      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;906      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));907      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;908909      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);910911      expect(unqRandomAccountAsset).to.be.null;912      913      balanceForeignUnqTokenFinal = 0n;914915      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;916      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));917      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;918    });919920    await helper.wait.newBlocks(3);921922    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);923    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;924    expect(actuallyDelivered > 0).to.be.true;925926    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));927928    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;929    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));930    expect(unqFees == 0n).to.be.true;931  });932});