--- 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 { log::trace!(target: "fassets::get_currency_id", "call"); - Some(AssetIds::ForeignAssetId( - Pallet::::location_to_currency_ids(multi_location).unwrap_or(0), - )) + Pallet::::location_to_currency_ids(multi_location).map(|id| AssetIds::ForeignAssetId(id)) } } --- 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(PhantomData<(AccountId, ForeignAssets)>); +/// No teleports are allowed +pub struct NoTeleports(PhantomData<(AccountId, ForeignAssets)>); impl Contains<>::AssetId> - for NonZeroIssuance + for NoTeleports where ForeignAssets: fungibles::Inspect, { - fn contains(id: &>::AssetId) -> bool { - !ForeignAssets::total_issuance(*id).is_zero() + fn contains(_id: &>::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, + // No teleports are allowed + NoTeleports, // The account to use for tracking teleports. CheckingAccount, >; --- 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); + } _ => {} }); --- 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; --- 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, url, code); }; +export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevWestmintHelper, url, code); +}; + +export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevWestmintHelper, url, code); +}; + export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise) => Promise) => { return usingPlaygroundsGeneral(DevRelayHelper, url, code); }; --- 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; --- 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 extends HelperGroup { - 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 { + 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; xcm: XcmGroup; 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'), --- 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; + }); }); }); --- 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); --- 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);