difftreelog
Merge pull request #801 from UniqueNetwork/fix/xcm-qtz-unq-tests
in: master
Fix xcm
10 files changed
pallets/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))
}
}
runtime/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,
>;
runtime/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);
+ }
_ => {}
});
tests/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;
tests/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);
};
tests/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;
tests/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'),
tests/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;
+ });
});
});
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth1// 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 {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const KARURA_CHAIN = 2000;25const MOONRIVER_CHAIN = 2023;2627const relayUrl = config.relayUrl;28const karuraUrl = config.karuraUrl;29const moonriverUrl = config.moonriverUrl;3031const KARURA_DECIMALS = 12;3233const TRANSFER_AMOUNT = 2000000000000000000000000n;3435describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {36 let alice: IKeyringPair;37 let randomAccount: IKeyringPair;3839 let balanceQuartzTokenInit: bigint;40 let balanceQuartzTokenMiddle: bigint;41 let balanceQuartzTokenFinal: bigint;42 let balanceKaruraTokenInit: bigint;43 let balanceKaruraTokenMiddle: bigint;44 let balanceKaruraTokenFinal: bigint;45 let balanceQuartzForeignTokenInit: bigint;46 let balanceQuartzForeignTokenMiddle: bigint;47 let balanceQuartzForeignTokenFinal: 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 usingKaruraPlaygrounds(karuraUrl, async (helper) => {56 const destination = {57 V0: {58 X2: [59 'Parent',60 {61 Parachain: QUARTZ_CHAIN,62 },63 ],64 },65 };6667 const metadata = {68 name: 'QTZ',69 symbol: 'QTZ',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 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);77 balanceQuartzForeignTokenInit = 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 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);83 });84 });8586 itSub('Should connect and send QTZ to Karura', async ({helper}) => {87 const destination = {88 V0: {89 X2: [90 'Parent',91 {92 Parachain: KARURA_CHAIN,93 },94 ],95 },96 };9798 const beneficiary = {99 V0: {100 X1: {101 AccountId32: {102 network: 'Any',103 id: randomAccount.addressRaw,104 },105 },106 },107 };108109 const assets = {110 V1: [111 {112 id: {113 Concrete: {114 parents: 0,115 interior: 'Here',116 },117 },118 fun: {119 Fungible: TRANSFER_AMOUNT,120 },121 },122 ],123 };124125 const feeAssetItem = 0;126 const weightLimit = 5000000000;127128 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, weightLimit);129 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);130131 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;132 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));133 expect(qtzFees > 0n).to.be.true;134135 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {136 await helper.wait.newBlocks(3);137 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});138 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);139140 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;141 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;142143 console.log(144 '[Quartz -> Karura] transaction fees on Karura: %s KAR',145 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),146 );147 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));148 expect(karFees == 0n).to.be.true;149 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;150 });151 });152153 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {154 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {155 const destination = {156 V1: {157 parents: 1,158 interior: {159 X2: [160 {Parachain: QUARTZ_CHAIN},161 {162 AccountId32: {163 network: 'Any',164 id: randomAccount.addressRaw,165 },166 },167 ],168 },169 },170 };171172 const id = {173 ForeignAsset: 0,174 };175176 const destWeight = 50000000;177178 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, destWeight);179 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);180 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);181182 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;183 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;184185 console.log(186 '[Karura -> Quartz] transaction fees on Karura: %s KAR',187 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),188 );189 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));190191 expect(karFees > 0).to.be.true;192 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;193 });194195 await helper.wait.newBlocks(3);196197 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);198 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;199 expect(actuallyDelivered > 0).to.be.true;200201 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));202203 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;204 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));205 expect(qtzFees == 0n).to.be.true;206 });207});208209// These tests are relevant only when the foreign asset pallet is disabled210describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {211 let alice: IKeyringPair;212213 before(async () => {214 await usingPlaygrounds(async (_helper, privateKey) => {215 alice = await privateKey('//Alice');216 });217 });218219 itSub('Quartz rejects tokens from the Relay', async ({helper}) => {220 await usingRelayPlaygrounds(relayUrl, async (helper) => {221 const destination = {222 V1: {223 parents: 0,224 interior: {X1: {225 Parachain: QUARTZ_CHAIN,226 },227 },228 }};229230 const beneficiary = {231 V1: {232 parents: 0,233 interior: {X1: {234 AccountId32: {235 network: 'Any',236 id: alice.addressRaw,237 },238 }},239 },240 };241242 const assets = {243 V1: [244 {245 id: {246 Concrete: {247 parents: 0,248 interior: 'Here',249 },250 },251 fun: {252 Fungible: 50_000_000_000_000_000n,253 },254 },255 ],256 };257258 const feeAssetItem = 0;259 const weightLimit = 5_000_000_000;260261 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, weightLimit);262 });263264 const maxWaitBlocks = 3;265266 const dmpQueueExecutedDownward = await helper.wait.event(maxWaitBlocks, 'dmpQueue', 'ExecutedDownward');267268 expect(269 dmpQueueExecutedDownward != null,270 '[Relay] dmpQueue.ExecutedDownward event is expected',271 ).to.be.true;272273 const event = dmpQueueExecutedDownward!.event;274 const outcome = event.data[1] as XcmV2TraitsOutcome;275276 expect(277 outcome.isIncomplete,278 '[Relay] The outcome of the XCM should be `Incomplete`',279 ).to.be.true;280281 const incomplete = outcome.asIncomplete;282 expect(283 incomplete[1].toString() == 'AssetNotFound',284 '[Relay] The XCM error should be `AssetNotFound`',285 ).to.be.true;286 });287288 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {289 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {290 const destination = {291 V1: {292 parents: 1,293 interior: {294 X2: [295 {Parachain: QUARTZ_CHAIN},296 {297 AccountId32: {298 network: 'Any',299 id: alice.addressRaw,300 },301 },302 ],303 },304 },305 };306307 const id = {308 Token: 'KAR',309 };310311 const destWeight = 50000000;312313 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, destWeight);314 });315316 const maxWaitBlocks = 3;317318 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');319320 expect(321 xcmpQueueFailEvent != null,322 '[Karura] xcmpQueue.FailEvent event is expected',323 ).to.be.true;324325 const event = xcmpQueueFailEvent!.event;326 const outcome = event.data[1] as XcmV2TraitsError;327328 expect(329 outcome.isUntrustedReserveLocation,330 '[Karura] The XCM error should be `UntrustedReserveLocation`',331 ).to.be.true;332 });333});334335describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {336 // Quartz constants337 let quartzDonor: IKeyringPair;338 let quartzAssetLocation;339340 let randomAccountQuartz: IKeyringPair;341 let randomAccountMoonriver: IKeyringPair;342343 // Moonriver constants344 let assetId: string;345346 const councilVotingThreshold = 2;347 const technicalCommitteeThreshold = 2;348 const votingPeriod = 3;349 const delayPeriod = 0;350351 const quartzAssetMetadata = {352 name: 'xcQuartz',353 symbol: 'xcQTZ',354 decimals: 18,355 isFrozen: false,356 minimalBalance: 1n,357 };358359 let balanceQuartzTokenInit: bigint;360 let balanceQuartzTokenMiddle: bigint;361 let balanceQuartzTokenFinal: bigint;362 let balanceForeignQtzTokenInit: bigint;363 let balanceForeignQtzTokenMiddle: bigint;364 let balanceForeignQtzTokenFinal: bigint;365 let balanceMovrTokenInit: bigint;366 let balanceMovrTokenMiddle: bigint;367 let balanceMovrTokenFinal: bigint;368369 before(async () => {370 await usingPlaygrounds(async (helper, privateKey) => {371 quartzDonor = await privateKey('//Alice');372 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);373374 balanceForeignQtzTokenInit = 0n;375 });376377 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {378 const alithAccount = helper.account.alithAccount();379 const baltatharAccount = helper.account.baltatharAccount();380 const dorothyAccount = helper.account.dorothyAccount();381382 randomAccountMoonriver = helper.account.create();383384 // >>> Sponsoring Dorothy >>>385 console.log('Sponsoring Dorothy.......');386 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);387 console.log('Sponsoring Dorothy.......DONE');388 // <<< Sponsoring Dorothy <<<389390 quartzAssetLocation = {391 XCM: {392 parents: 1,393 interior: {X1: {Parachain: QUARTZ_CHAIN}},394 },395 };396 const existentialDeposit = 1n;397 const isSufficient = true;398 const unitsPerSecond = 1n;399 const numAssetsWeightHint = 0;400401 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({402 location: quartzAssetLocation,403 metadata: quartzAssetMetadata,404 existentialDeposit,405 isSufficient,406 unitsPerSecond,407 numAssetsWeightHint,408 });409 const proposalHash = blake2AsHex(encodedProposal);410411 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);412 console.log('Encoded length %d', encodedProposal.length);413 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);414415 // >>> Note motion preimage >>>416 console.log('Note motion preimage.......');417 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);418 console.log('Note motion preimage.......DONE');419 // <<< Note motion preimage <<<420421 // >>> Propose external motion through council >>>422 console.log('Propose external motion through council.......');423 const externalMotion = helper.democracy.externalProposeMajority(proposalHash);424 const encodedMotion = externalMotion?.method.toHex() || '';425 const motionHash = blake2AsHex(encodedMotion);426 console.log('Motion hash is %s', motionHash);427428 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);429430 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;431 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);432 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);433434 await helper.collective.council.close(dorothyAccount, motionHash, councilProposalIdx, 1_000_000_000, externalMotion.encodedLength);435 console.log('Propose external motion through council.......DONE');436 // <<< Propose external motion through council <<<437438 // >>> Fast track proposal through technical committee >>>439 console.log('Fast track proposal through technical committee.......');440 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);441 const encodedFastTrack = fastTrack?.method.toHex() || '';442 const fastTrackHash = blake2AsHex(encodedFastTrack);443 console.log('FastTrack hash is %s', fastTrackHash);444445 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);446447 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;448 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);449 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);450451 await helper.collective.techCommittee.close(baltatharAccount, fastTrackHash, techProposalIdx, 1_000_000_000, fastTrack.encodedLength);452 console.log('Fast track proposal through technical committee.......DONE');453 // <<< Fast track proposal through technical committee <<<454455 // >>> Referendum voting >>>456 console.log('Referendum voting.......');457 await helper.democracy.referendumVote(dorothyAccount, 0, {458 balance: 10_000_000_000_000_000_000n,459 vote: {aye: true, conviction: 1},460 });461 console.log('Referendum voting.......DONE');462 // <<< Referendum voting <<<463464 // >>> Acquire Quartz AssetId Info on Moonriver >>>465 console.log('Acquire Quartz AssetId Info on Moonriver.......');466467 // Wait for the democracy execute468 await helper.wait.newBlocks(5);469470 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();471472 console.log('QTZ asset ID is %s', assetId);473 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');474 // >>> Acquire Quartz AssetId Info on Moonriver >>>475476 // >>> Sponsoring random Account >>>477 console.log('Sponsoring random Account.......');478 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);479 console.log('Sponsoring random Account.......DONE');480 // <<< Sponsoring random Account <<<481482 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);483 });484485 await usingPlaygrounds(async (helper) => {486 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);487 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);488 });489 });490491 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {492 const currencyId = {493 NativeAssetId: 'Here',494 };495 const dest = {496 V1: {497 parents: 1,498 interior: {499 X2: [500 {Parachain: MOONRIVER_CHAIN},501 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},502 ],503 },504 },505 };506 const amount = TRANSFER_AMOUNT;507 const destWeight = 850000000;508509 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, destWeight);510511 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);512 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;513514 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;515 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));516 expect(transactionFees > 0).to.be.true;517518 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {519 await helper.wait.newBlocks(3);520521 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);522523 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;524 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));525 expect(movrFees == 0n).to.be.true;526527 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);528 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;529 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));530 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;531 });532 });533534 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {535 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {536 const asset = {537 V1: {538 id: {539 Concrete: {540 parents: 1,541 interior: {542 X1: {Parachain: QUARTZ_CHAIN},543 },544 },545 },546 fun: {547 Fungible: TRANSFER_AMOUNT,548 },549 },550 };551 const destination = {552 V1: {553 parents: 1,554 interior: {555 X2: [556 {Parachain: QUARTZ_CHAIN},557 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},558 ],559 },560 },561 };562 const destWeight = 50000000;563564 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, destWeight);565566 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);567568 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;569 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));570 expect(movrFees > 0).to.be.true;571572 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);573574 expect(qtzRandomAccountAsset).to.be.null;575576 balanceForeignQtzTokenFinal = 0n;577578 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;579 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));580 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;581 });582583 await helper.wait.newBlocks(3);584585 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);586 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;587 expect(actuallyDelivered > 0).to.be.true;588589 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));590591 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;592 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));593 expect(qtzFees == 0n).to.be.true;594 });595});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, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds} from '../util';2223const QUARTZ_CHAIN = 2095;24const STATEMINE_CHAIN = 1000;25const KARURA_CHAIN = 2000;26const MOONRIVER_CHAIN = 2023;2728const STATEMINE_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemineUrl = config.statemineUrl;32const karuraUrl = config.karuraUrl;33const moonriverUrl = config.moonriverUrl;3435const RELAY_DECIMALS = 12;36const STATEMINE_DECIMALS = 12;37const KARURA_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 Statemine', () => {53 let alice: IKeyringPair;54 let bob: IKeyringPair;55 56 let balanceStmnBefore: bigint;57 let balanceStmnAfter: bigint;5859 let balanceQuartzBefore: bigint;60 let balanceQuartzAfter: bigint;61 let balanceQuartzFinal: 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 Statemine(t) funds donor75 });7677 await usingRelayPlaygrounds(relayUrl, async (helper) => {78 // Fund accounts on Statemine(t)79 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);80 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);81 });8283 await usingStateminePlaygrounds(statemineUrl, 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 Statemine(t).107 // The sovereign account should be created before any action108 // (the assets pallet on Statemine(t) check if the sovereign account exists)109 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_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: STATEMINE_CHAIN,121 },122 {123 PalletInstance: STATEMINE_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 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);141 });142143144 // Providing the relay currency to the quartz 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: QUARTZ_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 Statemine to Quartz', async ({helper}) => {192 await usingStateminePlaygrounds(statemineUrl, async (helper) => {193 const dest = {194 V1: {195 parents: 1,196 interior: {X1: {197 Parachain: QUARTZ_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: STATEMINE_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 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',248 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_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 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);262263 console.log(264 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',265 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),266 );267 console.log(268 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',269 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),270 ); 271 // commission has not paid in USDT token272 expect(free).to.be.equal(TRANSFER_AMOUNT);273 // ... and parachain native token274 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;275 });276277 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {278 const destination = {279 V1: {280 parents: 1,281 interior: {X2: [282 {283 Parachain: STATEMINE_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 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);317 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter));318 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;319320 await usingStateminePlaygrounds(statemineUrl, 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 Quartz', 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: QUARTZ_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 wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;382 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;383 console.log(384 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',385 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),386 );387 console.log(388 '[Relay (Westend) -> Quartz] transaction fees: %s WND',389 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),390 );391 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);392 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;393 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ 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('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', 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('[Quartz -> 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 Karura', () => {445 let alice: IKeyringPair;446 let randomAccount: IKeyringPair;447448 let balanceQuartzTokenInit: bigint;449 let balanceQuartzTokenMiddle: bigint;450 let balanceQuartzTokenFinal: bigint;451 let balanceKaruraTokenInit: bigint;452 let balanceKaruraTokenMiddle: bigint;453 let balanceKaruraTokenFinal: bigint;454 let balanceQuartzForeignTokenInit: bigint;455 let balanceQuartzForeignTokenMiddle: bigint;456 let balanceQuartzForeignTokenFinal: 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 usingKaruraPlaygrounds(karuraUrl, async (helper) => {465 const destination = {466 V0: {467 X2: [468 'Parent',469 {470 Parachain: QUARTZ_CHAIN,471 },472 ],473 },474 };475476 const metadata = {477 name: 'QTZ',478 symbol: 'QTZ',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 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);486 balanceQuartzForeignTokenInit = 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 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);492 });493 });494495 itSub('Should connect and send QTZ to Karura', async ({helper}) => {496 const destination = {497 V0: {498 X2: [499 'Parent',500 {501 Parachain: KARURA_CHAIN,502 },503 ],504 },505 };506507 const beneficiary = {508 V0: {509 X1: {510 AccountId32: {511 network: 'Any',512 id: randomAccount.addressRaw,513 },514 },515 },516 };517518 const assets = {519 V1: [520 {521 id: {522 Concrete: {523 parents: 0,524 interior: 'Here',525 },526 },527 fun: {528 Fungible: TRANSFER_AMOUNT,529 },530 },531 ],532 };533534 const feeAssetItem = 0;535536 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');537 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);538539 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;540 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;541 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));542543 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {544 await helper.wait.newBlocks(3);545 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});546 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);547548 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;549 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;550551 console.log(552 '[Quartz -> Karura] transaction fees on Karura: %s KAR',553 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),554 );555 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));556 expect(karFees == 0n).to.be.true;557 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;558 });559 });560561 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {562 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {563 const destination = {564 V1: {565 parents: 1,566 interior: {567 X2: [568 {Parachain: QUARTZ_CHAIN},569 {570 AccountId32: {571 network: 'Any',572 id: randomAccount.addressRaw,573 },574 },575 ],576 },577 },578 };579580 const id = {581 ForeignAsset: 0,582 };583584 await helper.xTokens.transfer(randomAccount, id, TRANSFER_AMOUNT, destination, 'Unlimited');585 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);586 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);587588 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;589 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;590591 console.log(592 '[Karura -> Quartz] transaction fees on Karura: %s KAR',593 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),594 );595 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));596597 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;598 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;599 });600601 await helper.wait.newBlocks(3);602603 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);604 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;605 expect(actuallyDelivered > 0).to.be.true;606607 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));608609 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;610 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));611 expect(qtzFees == 0n).to.be.true;612 });613});614615// These tests are relevant only when the foreign asset pallet is disabled616describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {617 let alice: IKeyringPair;618619 before(async () => {620 await usingPlaygrounds(async (_helper, privateKey) => {621 alice = await privateKey('//Alice');622 });623 });624625 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {626 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {627 const destination = {628 V1: {629 parents: 1,630 interior: {631 X2: [632 {Parachain: QUARTZ_CHAIN},633 {634 AccountId32: {635 network: 'Any',636 id: alice.addressRaw,637 },638 },639 ],640 },641 },642 };643644 const id = {645 Token: 'KAR',646 };647648 await helper.xTokens.transfer(alice, id, 100_000_000_000n, destination, 'Unlimited');649 });650651 const maxWaitBlocks = 3;652653 const xcmpQueueFailEvent = await helper.wait.event(maxWaitBlocks, 'xcmpQueue', 'Fail');654655 expect(656 xcmpQueueFailEvent != null,657 '[Karura] xcmpQueue.FailEvent event is expected',658 ).to.be.true;659660 const event = xcmpQueueFailEvent!.event;661 const outcome = event.data[1] as XcmV2TraitsError;662663 expect(664 outcome.isFailedToTransactAsset,665 '[Karura] The XCM error should be `FailedToTransactAsset`',666 ).to.be.true;667 });668});669670describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {671 // Quartz constants672 let quartzDonor: IKeyringPair;673 let quartzAssetLocation;674675 let randomAccountQuartz: IKeyringPair;676 let randomAccountMoonriver: IKeyringPair;677678 // Moonriver constants679 let assetId: string;680681 const councilVotingThreshold = 2;682 const technicalCommitteeThreshold = 2;683 const votingPeriod = 3;684 const delayPeriod = 0;685686 const quartzAssetMetadata = {687 name: 'xcQuartz',688 symbol: 'xcQTZ',689 decimals: 18,690 isFrozen: false,691 minimalBalance: 1n,692 };693694 let balanceQuartzTokenInit: bigint;695 let balanceQuartzTokenMiddle: bigint;696 let balanceQuartzTokenFinal: bigint;697 let balanceForeignQtzTokenInit: bigint;698 let balanceForeignQtzTokenMiddle: bigint;699 let balanceForeignQtzTokenFinal: bigint;700 let balanceMovrTokenInit: bigint;701 let balanceMovrTokenMiddle: bigint;702 let balanceMovrTokenFinal: bigint;703704 before(async () => {705 await usingPlaygrounds(async (helper, privateKey) => {706 quartzDonor = await privateKey('//Alice');707 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], quartzDonor);708709 balanceForeignQtzTokenInit = 0n;710 });711712 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {713 const alithAccount = helper.account.alithAccount();714 const baltatharAccount = helper.account.baltatharAccount();715 const dorothyAccount = helper.account.dorothyAccount();716717 randomAccountMoonriver = helper.account.create();718719 // >>> Sponsoring Dorothy >>>720 console.log('Sponsoring Dorothy.......');721 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);722 console.log('Sponsoring Dorothy.......DONE');723 // <<< Sponsoring Dorothy <<<724725 quartzAssetLocation = {726 XCM: {727 parents: 1,728 interior: {X1: {Parachain: QUARTZ_CHAIN}},729 },730 };731 const existentialDeposit = 1n;732 const isSufficient = true;733 const unitsPerSecond = 1n;734 const numAssetsWeightHint = 0;735736 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({737 location: quartzAssetLocation,738 metadata: quartzAssetMetadata,739 existentialDeposit,740 isSufficient,741 unitsPerSecond,742 numAssetsWeightHint,743 });744 const proposalHash = blake2AsHex(encodedProposal);745746 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);747 console.log('Encoded length %d', encodedProposal.length);748 console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);749750 // >>> Note motion preimage >>>751 console.log('Note motion preimage.......');752 await helper.democracy.notePreimage(baltatharAccount, encodedProposal);753 console.log('Note motion preimage.......DONE');754 // <<< Note motion preimage <<<755756 // >>> Propose external motion through council >>>757 console.log('Propose external motion through council.......');758 const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});759 const encodedMotion = externalMotion?.method.toHex() || '';760 const motionHash = blake2AsHex(encodedMotion);761 console.log('Motion hash is %s', motionHash);762763 await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);764765 const councilProposalIdx = await helper.collective.council.proposalCount() - 1;766 await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);767 await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);768769 await helper.collective.council.close(770 dorothyAccount,771 motionHash,772 councilProposalIdx,773 {774 refTime: 1_000_000_000,775 proofSize: 1_000_000,776 },777 externalMotion.encodedLength,778 );779 console.log('Propose external motion through council.......DONE');780 // <<< Propose external motion through council <<<781782 // >>> Fast track proposal through technical committee >>>783 console.log('Fast track proposal through technical committee.......');784 const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);785 const encodedFastTrack = fastTrack?.method.toHex() || '';786 const fastTrackHash = blake2AsHex(encodedFastTrack);787 console.log('FastTrack hash is %s', fastTrackHash);788789 await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);790791 const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;792 await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);793 await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);794795 await helper.collective.techCommittee.close(796 baltatharAccount,797 fastTrackHash,798 techProposalIdx,799 {800 refTime: 1_000_000_000,801 proofSize: 1_000_000,802 },803 fastTrack.encodedLength,804 );805 console.log('Fast track proposal through technical committee.......DONE');806 // <<< Fast track proposal through technical committee <<<807808 // >>> Referendum voting >>>809 console.log('Referendum voting.......');810 await helper.democracy.referendumVote(dorothyAccount, 0, {811 balance: 10_000_000_000_000_000_000n,812 vote: {aye: true, conviction: 1},813 });814 console.log('Referendum voting.......DONE');815 // <<< Referendum voting <<<816817 // >>> Acquire Quartz AssetId Info on Moonriver >>>818 console.log('Acquire Quartz AssetId Info on Moonriver.......');819820 // Wait for the democracy execute821 await helper.wait.newBlocks(5);822823 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();824825 console.log('QTZ asset ID is %s', assetId);826 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');827 // >>> Acquire Quartz AssetId Info on Moonriver >>>828829 // >>> Sponsoring random Account >>>830 console.log('Sponsoring random Account.......');831 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);832 console.log('Sponsoring random Account.......DONE');833 // <<< Sponsoring random Account <<<834835 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);836 });837838 await usingPlaygrounds(async (helper) => {839 await helper.balance.transferToSubstrate(quartzDonor, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);840 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);841 });842 });843844 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {845 const currencyId = {846 NativeAssetId: 'Here',847 };848 const dest = {849 V1: {850 parents: 1,851 interior: {852 X2: [853 {Parachain: MOONRIVER_CHAIN},854 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},855 ],856 },857 },858 };859 const amount = TRANSFER_AMOUNT;860861 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');862863 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);864 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;865866 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;867 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));868 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;869870 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {871 await helper.wait.newBlocks(3);872873 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);874875 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;876 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));877 expect(movrFees == 0n).to.be.true;878879 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);880 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;881 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));882 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;883 });884 });885886 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {887 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {888 const asset = {889 V1: {890 id: {891 Concrete: {892 parents: 1,893 interior: {894 X1: {Parachain: QUARTZ_CHAIN},895 },896 },897 },898 fun: {899 Fungible: TRANSFER_AMOUNT,900 },901 },902 };903 const destination = {904 V1: {905 parents: 1,906 interior: {907 X2: [908 {Parachain: QUARTZ_CHAIN},909 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},910 ],911 },912 },913 };914915 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');916917 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);918919 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;920 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));921 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;922923 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);924925 expect(qtzRandomAccountAsset).to.be.null;926927 balanceForeignQtzTokenFinal = 0n;928929 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;930 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));931 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;932 });933934 await helper.wait.newBlocks(3);935936 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);937 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;938 expect(actuallyDelivered > 0).to.be.true;939940 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));941942 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;943 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));944 expect(qtzFees == 0n).to.be.true;945 });946});tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmUnique.test.ts
+++ b/tests/src/xcm/xcmUnique.test.ts
@@ -17,21 +17,430 @@
import {IKeyringPair} from '@polkadot/types/types';
import {blake2AsHex} from '@polkadot/util-crypto';
import config from '../config';
-import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds} from '../util';
+import {XcmV2TraitsError} from '../interfaces';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds} from '../util';
const UNIQUE_CHAIN = 2037;
+const STATEMINT_CHAIN = 1000;
const ACALA_CHAIN = 2000;
const MOONBEAM_CHAIN = 2004;
+const STATEMINT_PALLET_INSTANCE = 50;
+
const relayUrl = config.relayUrl;
+const statemintUrl = config.statemintUrl;
const acalaUrl = config.acalaUrl;
const moonbeamUrl = config.moonbeamUrl;
+const RELAY_DECIMALS = 12;
+const STATEMINT_DECIMALS = 12;
const ACALA_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 Statemint', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ let balanceStmnBefore: bigint;
+ let balanceStmnAfter: bigint;
+
+ let balanceUniqueBefore: bigint;
+ let balanceUniqueAfter: bigint;
+ let balanceUniqueFinal: 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 Statemint funds donor
+ });
+
+ await usingRelayPlaygrounds(relayUrl, async (helper) => {
+ // Fund accounts on Statemint
+ await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);
+ await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);
+ });
+
+ await usingStatemintPlaygrounds(statemintUrl, 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 Statemint.
+ // The sovereign account should be created before any action
+ // (the assets pallet on Statemint check if the sovereign account exists)
+ const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
+ await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
+ });
+
+
+ await usingPlaygrounds(async (helper) => {
+ const location = {
+ V1: {
+ parents: 1,
+ interior: {X3: [
+ {
+ Parachain: STATEMINT_CHAIN,
+ },
+ {
+ PalletInstance: STATEMINT_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);
+ balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);
+ });
+
+
+ // Providing the relay currency to the unique sender account
+ // (fee for USDT XCM are paid in relay tokens)
+ await usingRelayPlaygrounds(relayUrl, async (helper) => {
+ const destination = {
+ V1: {
+ parents: 0,
+ interior: {X1: {
+ Parachain: UNIQUE_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 Statemint to Unique', async ({helper}) => {
+ await usingStatemintPlaygrounds(statemintUrl, async (helper) => {
+ const dest = {
+ V1: {
+ parents: 1,
+ interior: {X1: {
+ Parachain: UNIQUE_CHAIN,
+ },
+ },
+ }};
+
+ const beneficiary = {
+ V1: {
+ parents: 0,
+ interior: {X1: {
+ AccountId32: {
+ network: 'Any',
+ id: alice.addressRaw,
+ },
+ }},
+ },
+ };
+
+ const assets = {
+ V1: [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: {
+ X2: [
+ {
+ PalletInstance: STATEMINT_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(
+ '[Statemint -> Unique] transaction fees on Statemint: %s WND',
+ helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_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});
+
+ balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);
+
+ console.log(
+ '[Statemint -> Unique] transaction fees on Unique: %s USDT',
+ helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
+ );
+ console.log(
+ '[Statemint -> Unique] transaction fees on Unique: %s UNQ',
+ helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),
+ );
+ // commission has not paid in USDT token
+ expect(free).to.be.equal(TRANSFER_AMOUNT);
+ // ... and parachain native token
+ expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;
+ });
+
+ itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {
+ const destination = {
+ V1: {
+ parents: 1,
+ interior: {X2: [
+ {
+ Parachain: STATEMINT_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
+ balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);
+ console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueFinal - balanceUniqueAfter));
+ expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;
+
+ await usingStatemintPlaygrounds(statemintUrl, 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 Unique', 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: UNIQUE_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 wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
+ const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
+ console.log(
+ '[Relay (Westend) -> Unique] transaction fees: %s UNQ',
+ helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),
+ );
+ console.log(
+ '[Relay (Westend) -> Unique] transaction fees: %s WND',
+ helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),
+ );
+ console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);
+ expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;
+ expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ 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('[Unique -> Relay (Westend)] transaction fees: %s UNQ', 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('[Unique -> 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 Acala', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
@@ -124,15 +533,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');
balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));
- expect(unqFees > 0n).to.be.true;
+ expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
await helper.wait.newBlocks(3);
@@ -176,10 +583,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');
balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);
balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);
@@ -192,7 +596,7 @@
);
console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));
- expect(acaFees > 0).to.be.true;
+ expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;
expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;
});
@@ -219,76 +623,7 @@
alice = await privateKey('//Alice');
});
});
-
- itSub('Unique rejects tokens from the Relay', async ({helper}) => {
- await usingRelayPlaygrounds(relayUrl, async (helper) => {
- const destination = {
- V1: {
- parents: 0,
- interior: {X1: {
- Parachain: UNIQUE_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('Unique rejects ACA tokens from Acala', async ({helper}) => {
await usingAcalaPlaygrounds(acalaUrl, async (helper) => {
const destination = {
@@ -312,9 +647,7 @@
Token: 'ACA',
};
- 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;
@@ -330,8 +663,8 @@
const outcome = event.data[1] as XcmV2TraitsError;
expect(
- outcome.isUntrustedReserveLocation,
- '[Acala] The XCM error should be `UntrustedReserveLocation`',
+ outcome.isFailedToTransactAsset,
+ '[Acala] The XCM error should be `FailedToTransactAsset`',
).to.be.true;
});
});
@@ -508,16 +841,15 @@
},
};
const amount = TRANSFER_AMOUNT;
- const destWeight = 850000000;
- await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, destWeight);
+ await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');
balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);
expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;
const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));
- expect(transactionFees > 0).to.be.true;
+ expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {
await helper.wait.newBlocks(3);
@@ -572,7 +904,7 @@
const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;
console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));
- expect(glmrFees > 0).to.be.true;
+ expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;
const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);