From 61fc26e2c8d23cd0edd6c434d4538fbc95030154 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Thu, 28 Sep 2023 08:48:17 +0000 Subject: [PATCH 01/19] test(xcm qtz): added `XcmTestHelper` --- tests/package.json | 2 + tests/src/xcm/lowLevelXcmQuartz.test.ts | 381 +++++++++++++++++++++++ tests/src/xcm/lowLevelXcmUnique.test.ts | 19 +- tests/src/xcm/xcm.types.ts | 398 +++++++++++++++++++++++- tests/src/xcm/xcmQuartz.test.ts | 20 +- 5 files changed, 788 insertions(+), 32 deletions(-) create mode 100644 tests/src/xcm/lowLevelXcmQuartz.test.ts diff --git a/tests/package.json b/tests/package.json index 48371c4636..3138fbe6a5 100644 --- a/tests/package.json +++ b/tests/package.json @@ -116,6 +116,8 @@ "testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts", "testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts", "testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts", + "testLowLevelXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmQuartz.test.ts", + "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts", "testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts", "testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000", "testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000", diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts new file mode 100644 index 0000000000..41a1258062 --- /dev/null +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -0,0 +1,381 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '../util'; +import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; +import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types'; + + +const testHelper = new XcmTestHelper('quartz'); + +describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { + let alice: IKeyringPair; + let randomAccount: IKeyringPair; + + let balanceQuartzTokenInit: bigint; + let balanceQuartzTokenMiddle: bigint; + let balanceQuartzTokenFinal: bigint; + let balanceKaruraTokenInit: bigint; + let balanceKaruraTokenMiddle: bigint; + let balanceKaruraTokenFinal: bigint; + let balanceQuartzForeignTokenInit: bigint; + let balanceQuartzForeignTokenMiddle: bigint; + let balanceQuartzForeignTokenFinal: bigint; + + // computed by a test transfer from prod Quartz to prod Karura. + // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9 + // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block) + const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n; + const karuraEps = 8n * 10n ** 16n; + + let karuraBackwardTransferAmount: bigint; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [randomAccount] = await helper.arrange.createAccounts([0n], alice); + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); + + await usingKaruraPlaygrounds(karuraUrl, async (helper) => { + const destination = { + V2: { + parents: 1, + interior: { + X1: { + Parachain: QUARTZ_CHAIN, + }, + }, + }, + }; + + const metadata = { + name: 'Quartz', + symbol: 'QTZ', + decimals: 18, + minimalBalance: 1000000000000000000n, + }; + + await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); + balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address); + balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); + }); + + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT); + balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address); + }); + }); + + itSub('Should connect and send QTZ to Karura', async () => { + await testHelper.sendUnqTo('karura', randomAccount); + }); + + itSub('Should connect to Karura and send QTZ back', async () => { + await testHelper.sendUnqBack('karura', alice, randomAccount); + }); + + itSub('Karura can send only up to its balance', async () => { + await testHelper.sendOnlyOwnedBalance('karura', alice); + }); +}); +// These tests are relevant only when +// the the corresponding foreign assets are not registered +describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => { + let alice: IKeyringPair; + let alith: IKeyringPair; + + const testAmount = 100_000_000_000n; + let quartzParachainJunction; + let quartzAccountJunction; + + let quartzParachainMultilocation: any; + let quartzAccountMultilocation: any; + let quartzCombinedMultilocation: any; + + let messageSent: any; + + const maxWaitBlocks = 3; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + + quartzParachainJunction = {Parachain: QUARTZ_CHAIN}; + quartzAccountJunction = { + AccountId32: { + network: 'Any', + id: alice.addressRaw, + }, + }; + + quartzParachainMultilocation = { + V2: { + parents: 1, + interior: { + X1: quartzParachainJunction, + }, + }, + }; + + quartzAccountMultilocation = { + V2: { + parents: 0, + interior: { + X1: quartzAccountJunction, + }, + }, + }; + + quartzCombinedMultilocation = { + V2: { + parents: 1, + interior: { + X2: [quartzParachainJunction, quartzAccountJunction], + }, + }, + }; + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); + + // eslint-disable-next-line require-await + await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + alith = helper.account.alithAccount(); + }); + }); + + itSub('Quartz rejects KAR tokens from Karura', async () => { + await testHelper.rejectNativeTokensFrom('karura', alice); + }); + + itSub('Quartz rejects MOVR tokens from Moonriver', async () => { + await testHelper.rejectNativeTokensFrom('moonriver', alice); + }); + + itSub('Quartz rejects SDN tokens from Shiden', async () => { + await testHelper.rejectNativeTokensFrom('shiden', alice); + }); +}); + +describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { + // Quartz constants + let alice: IKeyringPair; + let quartzAssetLocation; + + let randomAccountQuartz: IKeyringPair; + let randomAccountMoonriver: IKeyringPair; + + // Moonriver constants + let assetId: string; + + const quartzAssetMetadata = { + name: 'xcQuartz', + symbol: 'xcQTZ', + decimals: 18, + isFrozen: false, + minimalBalance: 1n, + }; + + let balanceQuartzTokenInit: bigint; + let balanceQuartzTokenMiddle: bigint; + let balanceQuartzTokenFinal: bigint; + let balanceForeignQtzTokenInit: bigint; + let balanceForeignQtzTokenMiddle: bigint; + let balanceForeignQtzTokenFinal: bigint; + let balanceMovrTokenInit: bigint; + let balanceMovrTokenMiddle: bigint; + let balanceMovrTokenFinal: bigint; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice); + + balanceForeignQtzTokenInit = 0n; + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); + + await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { + const alithAccount = helper.account.alithAccount(); + const baltatharAccount = helper.account.baltatharAccount(); + const dorothyAccount = helper.account.dorothyAccount(); + + randomAccountMoonriver = helper.account.create(); + + // >>> Sponsoring Dorothy >>> + console.log('Sponsoring Dorothy.......'); + await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring Dorothy.......DONE'); + // <<< Sponsoring Dorothy <<< + + quartzAssetLocation = { + XCM: { + parents: 1, + interior: {X1: {Parachain: QUARTZ_CHAIN}}, + }, + }; + const existentialDeposit = 1n; + const isSufficient = true; + const unitsPerSecond = 1n; + const numAssetsWeightHint = 0; + + const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ + location: quartzAssetLocation, + metadata: quartzAssetMetadata, + existentialDeposit, + isSufficient, + unitsPerSecond, + numAssetsWeightHint, + }); + + console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); + + await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); + + // >>> Acquire Quartz AssetId Info on Moonriver >>> + console.log('Acquire Quartz AssetId Info on Moonriver.......'); + + assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString(); + + console.log('QTZ asset ID is %s', assetId); + console.log('Acquire Quartz AssetId Info on Moonriver.......DONE'); + // >>> Acquire Quartz AssetId Info on Moonriver >>> + + // >>> Sponsoring random Account >>> + console.log('Sponsoring random Account.......'); + await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n); + console.log('Sponsoring random Account.......DONE'); + // <<< Sponsoring random Account <<< + + balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address); + }); + + await usingPlaygrounds(async (helper) => { + await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); + balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address); + }); + }); + + itSub('Should connect and send QTZ to Moonriver', async () => { + await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver); + }); + + itSub('Should connect to Moonriver and send QTZ back', async () => { + await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz); + }); + + itSub('Moonriver can send only up to its balance', async () => { + await testHelper.sendOnlyOwnedBalance('moonriver', alice); + }); + + itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => { + await testHelper.reserveTransferUNQfrom('moonriver', alice); + }); +}); + +describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { + let alice: IKeyringPair; + let sender: IKeyringPair; + + const QTZ_ASSET_ID_ON_SHIDEN = 1; + const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; + + // Quartz -> Shiden + const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden + const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? + const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ + const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens + + // Shiden -> Quartz + const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ + const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ + + let balanceAfterQuartzToShidenXCM: bigint; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [sender] = await helper.arrange.createAccounts([100n], alice); + console.log('sender', sender.address); + + // Set the default version to wrap the first message to other chains. + await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); + }); + + await usingShidenPlaygrounds(shidenUrl, async (helper) => { + console.log('1. Create foreign asset and metadata'); + // TODO update metadata with values from production + await helper.assets.create( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + alice.address, + QTZ_MINIMAL_BALANCE_ON_SHIDEN, + ); + + await helper.assets.setMetadata( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + 'Cross chain QTZ', + 'xcQTZ', + Number(QTZ_DECIMALS), + ); + + console.log('2. Register asset location on Shiden'); + const assetLocation = { + V2: { + parents: 1, + interior: { + X1: { + Parachain: QUARTZ_CHAIN, + }, + }, + }, + }; + + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); + + console.log('3. Set QTZ payment for XCM execution on Shiden'); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + + console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)'); + await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance); + }); + }); + + itSub('Should connect and send QTZ to Shiden', async () => { + await testHelper.sendUnqTo('shiden', sender); + }); + + itSub('Should connect to Shiden and send QTZ back', async () => { + await testHelper.sendUnqBack('shiden', alice, sender); + }); + + itSub('Shiden can send only up to its balance', async () => { + await testHelper.sendOnlyOwnedBalance('shiden', alice); + }); + + itSub('Should not accept reserve transfer of QTZ from Shiden', async () => { + await testHelper.reserveTransferUNQfrom('shiden', alice); + }); +}); diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index 7f11634deb..dadc44364d 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -20,7 +20,7 @@ import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usi import {Event} from '../util/playgrounds/unique.dev'; import {nToBigInt} from '@polkadot/util'; import {hexToString} from '@polkadot/util'; -import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types'; +import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types'; const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; @@ -34,6 +34,7 @@ let balanceUniqueTokenMiddle: bigint; let balanceUniqueTokenFinal: bigint; let unqFees: bigint; +const testHelper = new XcmTestHelper('unique'); async function genericSendUnqTo( networkName: keyof typeof NETWORKS, @@ -434,20 +435,20 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { }); itSub('Should connect and send UNQ to Polkadex', async () => { - await genericSendUnqTo('polkadex', randomAccount); + await testHelper.sendUnqTo('polkadex', randomAccount); }); itSub('Should connect to Polkadex and send UNQ back', async () => { - await genericSendUnqBack('polkadex', alice, randomAccount); + await testHelper.sendUnqBack('polkadex', alice, randomAccount); }); itSub('Polkadex can send only up to its balance', async () => { - await genericSendOnlyOwnedBalance('polkadex', alice); + await testHelper.sendOnlyOwnedBalance('polkadex', alice); }); itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => { - await genericReserveTransferUNQfrom('polkadex', alice); + await testHelper.reserveTransferUNQfrom('polkadex', alice); }); }); @@ -574,19 +575,19 @@ describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => { }); itSub('Should connect and send UNQ to Moonbeam', async () => { - await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam); + await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam); }); itSub('Should connect to Moonbeam and send UNQ back', async () => { - await genericSendUnqBack('moonbeam', alice, randomAccountUnique); + await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique); }); itSub('Moonbeam can send only up to its balance', async () => { - await genericSendOnlyOwnedBalance('moonbeam', alice); + await testHelper.sendOnlyOwnedBalance('moonbeam', alice); }); itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => { - await genericReserveTransferUNQfrom('moonbeam', alice); + await testHelper.reserveTransferUNQfrom('moonbeam', alice); }); }); diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index 6df7f247c9..cf6e6fd10c 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -1,4 +1,5 @@ -import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util'; +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingShidenPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; import config from '../config'; @@ -9,19 +10,39 @@ export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004); export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006); export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040); +export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095); +export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000); +export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000); +export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023); +export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007); + +export const relayUrl = config.relayUrl; +export const statemintUrl = config.statemintUrl; +export const statemineUrl = config.statemineUrl; + export const acalaUrl = config.acalaUrl; export const moonbeamUrl = config.moonbeamUrl; export const astarUrl = config.astarUrl; export const polkadexUrl = config.polkadexUrl; +export const karuraUrl = config.karuraUrl; +export const moonriverUrl = config.moonriverUrl; +export const shidenUrl = config.shidenUrl; + export const SAFE_XCM_VERSION = 3; -export const maxWaitBlocks = 6; +export const RELAY_DECIMALS = 12; +export const STATEMINE_DECIMALS = 12; +export const KARURA_DECIMALS = 12; +export const SHIDEN_DECIMALS = 18n; +export const QTZ_DECIMALS = 18n; export const ASTAR_DECIMALS = 18n; export const UNQ_DECIMALS = 18n; +export const maxWaitBlocks = 6; + export const uniqueMultilocation = { parents: 1, interior: { @@ -52,9 +73,15 @@ export const NETWORKS = { astar: usingAstarPlaygrounds, polkadex: usingPolkadexPlaygrounds, moonbeam: usingMoonbeamPlaygrounds, + moonriver: usingMoonriverPlaygrounds, + karura: usingKaruraPlaygrounds, + shiden: usingShidenPlaygrounds, } as const; +type NetworkNames = keyof typeof NETWORKS; + +type NativeRuntime = 'opal' | 'quartz' | 'unique'; -export function mapToChainId(networkName: keyof typeof NETWORKS) { +export function mapToChainId(networkName: keyof typeof NETWORKS): number { switch (networkName) { case 'acala': return ACALA_CHAIN; @@ -64,10 +91,16 @@ export function mapToChainId(networkName: keyof typeof NETWORKS) { return MOONBEAM_CHAIN; case 'polkadex': return POLKADEX_CHAIN; + case 'moonriver': + return MOONRIVER_CHAIN; + case 'karura': + return KARURA_CHAIN; + case 'shiden': + return SHIDEN_CHAIN; } } -export function mapToChainUrl(networkName: keyof typeof NETWORKS): string { +export function mapToChainUrl(networkName: NetworkNames): string { switch (networkName) { case 'acala': return acalaUrl; @@ -77,9 +110,364 @@ export function mapToChainUrl(networkName: keyof typeof NETWORKS): string { return moonbeamUrl; case 'polkadex': return polkadexUrl; + case 'moonriver': + return moonriverUrl; + case 'karura': + return karuraUrl; + case 'shiden': + return shidenUrl; } } -export function getDevPlayground(name: T) { +export function getDevPlayground(name: NetworkNames) { return NETWORKS[name]; +} + +export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; +const SENDER_BUDGET = 2n * TRANSFER_AMOUNT; +const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n; +const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT; +const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; + +export class XcmTestHelper { + private _balanceUniqueTokenInit: bigint = 0n; + private _balanceUniqueTokenMiddle: bigint = 0n; + private _balanceUniqueTokenFinal: bigint = 0n; + private _unqFees: bigint = 0n; + private _nativeRuntime: NativeRuntime; + + constructor(runtime: NativeRuntime) { + this._nativeRuntime = runtime; + } + + private _getNativeId() { + switch (this._nativeRuntime) { + case 'opal': + // To-Do + return 10; + case 'quartz': + return QUARTZ_CHAIN; + case 'unique': + return UNIQUE_CHAIN; + } + } + private _isAddress20FormatFor(network: NetworkNames) { + switch (network) { + case 'moonbeam': + case 'moonriver': + return true; + default: + return false; + } + } + + async sendUnqTo( + networkName: keyof typeof NETWORKS, + randomAccount: IKeyringPair, + randomAccountOnTargetChain = randomAccount, + ) { + const networkUrl = mapToChainUrl(networkName); + const targetPlayground = getDevPlayground(networkName); + await usingPlaygrounds(async (helper) => { + this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); + const destination = { + V2: { + parents: 1, + interior: { + X1: { + Parachain: mapToChainId(networkName), + }, + }, + }, + }; + + const beneficiary = { + V2: { + parents: 0, + interior: { + X1: ( + this._isAddress20FormatFor(networkName) ? + { + AccountKey20: { + network: 'Any', + key: randomAccountOnTargetChain.address, + }, + } + : + { + AccountId32: { + network: 'Any', + id: randomAccountOnTargetChain.addressRaw, + }, + } + ), + }, + }, + }; + + const assets = { + V2: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: TRANSFER_AMOUNT, + }, + }, + ], + }; + const feeAssetItem = 0; + + await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); + const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); + + this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT; + console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees)); + expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; + + await targetPlayground(networkUrl, async (helper) => { + /* + Since only the parachain part of the Polkadex + infrastructure is launched (without their + solochain validators), processing incoming + assets will lead to an error. + This error indicates that the Polkadex chain + received a message from the Unique network, + since the hash is being checked to ensure + it matches what was sent. + */ + if(networkName == 'polkadex') { + await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash); + } else { + await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash); + } + }); + + }); + } + + async sendUnqBack( + networkName: keyof typeof NETWORKS, + sudoer: IKeyringPair, + randomAccountOnUnq: IKeyringPair, + ) { + const networkUrl = mapToChainUrl(networkName); + + const targetPlayground = getDevPlayground(networkName); + await usingPlaygrounds(async (helper) => { + + const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( + randomAccountOnUnq.addressRaw, + { + Concrete: { + parents: 1, + interior: { + X1: {Parachain: this._getNativeId()}, + }, + }, + }, + SENDBACK_AMOUNT, + ); + + let xcmProgramSent: any; + + + await targetPlayground(networkUrl, async (helper) => { + if('getSudo' in helper) { + await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram); + xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } else if('fastDemocracy' in helper) { + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]); + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); + xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + }); + + await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash); + + this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address); + + expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN); + + }); + } + + async sendOnlyOwnedBalance( + networkName: keyof typeof NETWORKS, + sudoer: IKeyringPair, + ) { + const networkUrl = mapToChainUrl(networkName); + const targetPlayground = getDevPlayground(networkName); + + const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS); + + await usingPlaygrounds(async (helper) => { + const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName)); + await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance); + const moreThanTargetChainHas = 2n * targetChainBalance; + + const targetAccount = helper.arrange.createEmptyAccount(); + + const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( + targetAccount.addressRaw, + { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + moreThanTargetChainHas, + ); + + let maliciousXcmProgramSent: any; + + + await targetPlayground(networkUrl, async (helper) => { + if('getSudo' in helper) { + await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram); + maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } else if('fastDemocracy' in helper) { + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]); + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); + maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + }); + + await expectFailedToTransact(helper, maliciousXcmProgramSent); + + const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); + expect(targetAccountBalance).to.be.equal(0n); + }); + } + + async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) { + const networkUrl = mapToChainUrl(netwokrName); + const targetPlayground = getDevPlayground(netwokrName); + + await usingPlaygrounds(async (helper) => { + const testAmount = 10_000n * (10n ** UNQ_DECIMALS); + const targetAccount = helper.arrange.createEmptyAccount(); + + const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( + targetAccount.addressRaw, + { + Concrete: { + parents: 1, + interior: { + X1: { + Parachain: this._getNativeId(), + }, + }, + }, + }, + testAmount, + ); + + const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( + targetAccount.addressRaw, + { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + testAmount, + ); + + let maliciousXcmProgramFullIdSent: any; + let maliciousXcmProgramHereIdSent: any; + const maxWaitBlocks = 3; + + // Try to trick Unique using full UNQ identification + await targetPlayground(networkUrl, async (helper) => { + if('getSudo' in helper) { + await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId); + maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + // Moonbeam case + else if('fastDemocracy' in helper) { + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall); + + maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + }); + + + await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent); + + let accountBalance = await helper.balance.getSubstrate(targetAccount.address); + expect(accountBalance).to.be.equal(0n); + + // Try to trick Unique using shortened UNQ identification + await targetPlayground(networkUrl, async (helper) => { + if('getSudo' in helper) { + await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId); + maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + else if('fastDemocracy' in helper) { + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]); + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); + + maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + }); + + await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent); + + accountBalance = await helper.balance.getSubstrate(targetAccount.address); + expect(accountBalance).to.be.equal(0n); + }); + } + + async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) { + const networkUrl = mapToChainUrl(networkName); + const targetPlayground = getDevPlayground(networkName); + let messageSent: any; + + await usingPlaygrounds(async (helper) => { + const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( + helper.arrange.createEmptyAccount().addressRaw, + { + Concrete: { + parents: 1, + interior: { + X1: { + Parachain: mapToChainId(networkName), + }, + }, + }, + }, + TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT, + ); + await targetPlayground(networkUrl, async (helper) => { + if('getSudo' in helper) { + await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId); + messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } else if('fastDemocracy' in helper) { + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); + // Needed to bypass the call filter. + const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); + await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall); + + messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); + } + }); + await expectFailedToTransact(helper, messageSent); + }); + } + } \ No newline at end of file diff --git a/tests/src/xcm/xcmQuartz.test.ts b/tests/src/xcm/xcmQuartz.test.ts index 354dbfb6eb..fe7d1083b1 100644 --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -15,29 +15,13 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import config from '../config'; import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; +import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types'; -const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095); -const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000); -const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000); -const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023); -const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007); -const STATEMINE_PALLET_INSTANCE = 50; -const relayUrl = config.relayUrl; -const statemineUrl = config.statemineUrl; -const karuraUrl = config.karuraUrl; -const moonriverUrl = config.moonriverUrl; -const shidenUrl = config.shidenUrl; - -const RELAY_DECIMALS = 12; -const STATEMINE_DECIMALS = 12; -const KARURA_DECIMALS = 12; -const SHIDEN_DECIMALS = 18n; -const QTZ_DECIMALS = 18n; +const STATEMINE_PALLET_INSTANCE = 50; const TRANSFER_AMOUNT = 2000000000000000000000000n; From 8d26c73748339e76350add2ace1dafc2fa616947 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Tue, 26 Sep 2023 15:03:13 +0200 Subject: [PATCH 02/19] feat: allow xcm transact for sys,gov and utility --- runtime/common/config/xcm/mod.rs | 52 +++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/runtime/common/config/xcm/mod.rs b/runtime/common/config/xcm/mod.rs index 1add5d3de6..a825d729ab 100644 --- a/runtime/common/config/xcm/mod.rs +++ b/runtime/common/config/xcm/mod.rs @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . use frame_support::{ - traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError}, + traits::{Everything, Nothing, Get, ConstU32, ProcessMessageError, Contains}, parameter_types, }; use frame_system::EnsureRoot; @@ -162,6 +162,52 @@ where pub type Weigher = FixedWeightBounds; +pub struct XcmCallFilter; +impl XcmCallFilter { + fn allow_gov_and_sys_call(call: &RuntimeCall) -> bool { + match call { + RuntimeCall::System(..) + | RuntimeCall::Identity(..) + | RuntimeCall::Preimage(..) + | RuntimeCall::Democracy(..) + | RuntimeCall::Council(..) + | RuntimeCall::TechnicalCommittee(..) + | RuntimeCall::CouncilMembership(..) + | RuntimeCall::TechnicalCommitteeMembership(..) + | RuntimeCall::FellowshipCollective(..) + | RuntimeCall::FellowshipReferenda(..) => true, + _ => false, + } + } + + fn allow_utility_call(call: &RuntimeCall) -> bool { + match call { + RuntimeCall::Utility(pallet_utility::Call::batch { calls, .. }) => { + calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + } + RuntimeCall::Utility(pallet_utility::Call::batch_all { calls, .. }) => { + calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + } + RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) => { + Self::allow_gov_and_sys_call(call) + } + RuntimeCall::Utility(pallet_utility::Call::dispatch_as { call, .. }) => { + Self::allow_gov_and_sys_call(call) + } + RuntimeCall::Utility(pallet_utility::Call::force_batch { calls, .. }) => { + calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + } + _ => false, + } + } +} + +impl Contains for XcmCallFilter { + fn contains(call: &RuntimeCall) -> bool { + Self::allow_gov_and_sys_call(call) || Self::allow_utility_call(call) + } +} + pub struct XcmExecutorConfig(PhantomData); impl xcm_executor::Config for XcmExecutorConfig where @@ -191,9 +237,7 @@ where type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; - - // Deny all XCM Transacts. - type SafeCallFilter = Nothing; + type SafeCallFilter = XcmCallFilter; } #[cfg(feature = "runtime-benchmarks")] From 58b781a796e9e48b35f1ab8600500dbeee63922d Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 28 Sep 2023 16:14:02 +0200 Subject: [PATCH 03/19] feat: allow explicit unpaid execution from parent --- runtime/opal/src/xcm_barrier.rs | 17 ++++++++++++++--- runtime/quartz/src/xcm_barrier.rs | 7 ++++++- runtime/unique/src/xcm_barrier.rs | 7 ++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/runtime/opal/src/xcm_barrier.rs b/runtime/opal/src/xcm_barrier.rs index a9e9a6d450..44f664b78a 100644 --- a/runtime/opal/src/xcm_barrier.rs +++ b/runtime/opal/src/xcm_barrier.rs @@ -14,7 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -use frame_support::traits::Everything; -use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit}; +use frame_support::{match_types, traits::Everything}; +use xcm::latest::{Junctions::*, MultiLocation}; +use xcm_builder::{AllowTopLevelPaidExecutionFrom, TakeWeightCredit, AllowExplicitUnpaidExecutionFrom}; -pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom); +match_types! { + pub type ParentOnly: impl Contains = { + MultiLocation { parents: 1, interior: Here } + }; +} + +pub type Barrier = ( + TakeWeightCredit, + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, +); diff --git a/runtime/quartz/src/xcm_barrier.rs b/runtime/quartz/src/xcm_barrier.rs index 9aa370399b..a774924e30 100644 --- a/runtime/quartz/src/xcm_barrier.rs +++ b/runtime/quartz/src/xcm_barrier.rs @@ -18,12 +18,16 @@ use frame_support::{match_types, traits::Everything}; use xcm::latest::{Junctions::*, MultiLocation}; use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom, }; use crate::PolkadotXcm; match_types! { + pub type ParentOnly: impl Contains = { + MultiLocation { parents: 1, interior: Here } + }; + pub type ParentOrSiblings: impl Contains = { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(_) } @@ -32,6 +36,7 @@ match_types! { pub type Barrier = ( TakeWeightCredit, + AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, diff --git a/runtime/unique/src/xcm_barrier.rs b/runtime/unique/src/xcm_barrier.rs index 9aa370399b..a774924e30 100644 --- a/runtime/unique/src/xcm_barrier.rs +++ b/runtime/unique/src/xcm_barrier.rs @@ -18,12 +18,16 @@ use frame_support::{match_types, traits::Everything}; use xcm::latest::{Junctions::*, MultiLocation}; use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, AllowExplicitUnpaidExecutionFrom, }; use crate::PolkadotXcm; match_types! { + pub type ParentOnly: impl Contains = { + MultiLocation { parents: 1, interior: Here } + }; + pub type ParentOrSiblings: impl Contains = { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(_) } @@ -32,6 +36,7 @@ match_types! { pub type Barrier = ( TakeWeightCredit, + AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, From f61ec5aa50bb4fd53b862c9f3caa2bbf0d642d97 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 28 Sep 2023 16:17:14 +0200 Subject: [PATCH 04/19] feat: add dmpQueue event section --- tests/src/util/playgrounds/unique.dev.ts | 41 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/src/util/playgrounds/unique.dev.ts b/tests/src/util/playgrounds/unique.dev.ts index 499d2e8935..10569d9658 100644 --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -9,7 +9,7 @@ import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; import {EventRecord} from '@polkadot/types/interfaces'; import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types'; -import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup'; +import {FrameSystemEventRecord, XcmV2TraitsError, XcmV3TraitsOutcome} from '@polkadot/types/lookup'; import {SignerOptions, VoidFn} from '@polkadot/api/types'; import {Pallets} from '..'; import {spawnSync} from 'child_process'; @@ -260,6 +260,12 @@ export class Event { outcome: eventData(data, 1), })); }; + + static DmpQueue = class extends EventSection('dmpQueue') { + static ExecutedDownward = this.Method('ExecutedDownward', data => ({ + outcome: eventData(data, 1), + })); + }; } // eslint-disable-next-line @typescript-eslint/naming-convention @@ -559,6 +565,12 @@ export class DevRelayHelper extends RelayHelper { super(logger, options); this.wait = new WaitGroup(this); } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as DevRelayHelper; + } } export class DevWestmintHelper extends WestmintHelper { @@ -968,6 +980,31 @@ export class ArrangeGroup { ], }; } + + makeTransactProgram(info: {weightMultiplier: number, call: string}) { + return { + V3: [ + { + UnpaidExecution: { + weightLimit: 'Unlimited', + checkOrigin: null, + }, + }, + { + Transact: { + originKind: 'Superuser', + requireWeightAtMost: { + refTime: info.weightMultiplier * 200000000, + proofSize: info.weightMultiplier * 3000, + }, + call: { + encoded: info.call, + }, + }, + }, + ], + }; + } } class MoonbeamAccountGroup { @@ -1501,4 +1538,4 @@ function ScheduledUniqueHelper(Base: T) { ); } }; -} \ No newline at end of file +} From 0f67e15c3f4a9733871fe4b6b47599fef37530fa Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 28 Sep 2023 20:40:20 +0200 Subject: [PATCH 05/19] test: relay does root ops --- tests/src/xcm/lowLevelXcmQuartz.test.ts | 66 ++++++++++- tests/src/xcm/lowLevelXcmUnique.test.ts | 68 +++++++++++- tests/src/xcm/xcm.types.ts | 139 +++++++++++++++++++++++- 3 files changed, 268 insertions(+), 5 deletions(-) diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts index 41a1258062..1c1e52ad22 100644 --- a/tests/src/xcm/lowLevelXcmQuartz.test.ts +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '../util'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types'; @@ -379,3 +379,67 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { await testHelper.reserveTransferUNQfrom('shiden', alice); }); }); + +describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => { + let sudoer: IKeyringPair; + + before(async function () { + await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => { + sudoer = await privateKey('//Alice'); + }); + }); + + // At the moment there is no reliable way + // to establish the correspondence between the `ExecutedDownward` event + // and the relay's sent message due to `SetTopic` instruction + // containing an unpredictable topic silently added by the relay on the router level. + // This changes the message hash on arrival to our chain. + // + // See: + // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83 + // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36 + // + // Because of this, we insert time gaps between tests so + // different `ExecutedDownward` events won't interfere with each other. + afterEach(async () => { + await usingPlaygrounds(async (helper) => { + await helper.wait.newBlocks(3); + }); + }); + + itSub('The relay can set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain'); + }); + + itSub('The relay can batch set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch'); + }); + + itSub('The relay can batchAll set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll'); + }); + + itSub('The relay can forceBatch set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch'); + }); + + itSub('[negative] The relay cannot set balance', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain'); + }); + + itSub('[negative] The relay cannot set balance via batch', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch'); + }); + + itSub('[negative] The relay cannot set balance via batchAll', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll'); + }); + + itSub('[negative] The relay cannot set balance via forceBatch', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch'); + }); + + itSub('[negative] The relay cannot set balance via dispatchAs', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs'); + }); +}); diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index dadc44364d..ad69cbf828 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -16,11 +16,11 @@ import {IKeyringPair} from '@polkadot/types/types'; import config from '../config'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util'; import {Event} from '../util/playgrounds/unique.dev'; import {nToBigInt} from '@polkadot/util'; import {hexToString} from '@polkadot/util'; -import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types'; +import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types'; const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; @@ -672,3 +672,67 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { await genericReserveTransferUNQfrom('astar', alice); }); }); + +describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => { + let sudoer: IKeyringPair; + + before(async function () { + await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => { + sudoer = await privateKey('//Alice'); + }); + }); + + // At the moment there is no reliable way + // to establish the correspondence between the `ExecutedDownward` event + // and the relay's sent message due to `SetTopic` instruction + // containing an unpredictable topic silently added by the relay on the router level. + // This changes the message hash on arrival to our chain. + // + // See: + // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83 + // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36 + // + // Because of this, we insert time gaps between tests so + // different `ExecutedDownward` events won't interfere with each other. + afterEach(async () => { + await usingPlaygrounds(async (helper) => { + await helper.wait.newBlocks(3); + }); + }); + + itSub('The relay can set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain'); + }); + + itSub('The relay can batch set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch'); + }); + + itSub('The relay can batchAll set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll'); + }); + + itSub('The relay can forceBatch set storage', async () => { + await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch'); + }); + + itSub('[negative] The relay cannot set balance', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain'); + }); + + itSub('[negative] The relay cannot set balance via batch', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch'); + }); + + itSub('[negative] The relay cannot set balance via batchAll', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll'); + }); + + itSub('[negative] The relay cannot set balance via forceBatch', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch'); + }); + + itSub('[negative] The relay cannot set balance via dispatchAs', async () => { + await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs'); + }); +}); diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index cf6e6fd10c..5e6bd5bebb 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -1,7 +1,9 @@ import {IKeyringPair} from '@polkadot/types/types'; -import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingShidenPlaygrounds} from '../util'; +import {hexToString} from '@polkadot/util'; +import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; import config from '../config'; +import {blake2AsHex} from '@polkadot/util-crypto'; export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037); export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000); @@ -68,6 +70,16 @@ export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper && event.outcome.isUntrustedReserveLocation); }; +export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => { + // The correct messageHash for downward messages can't be reliably obtained + await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission); +}; + +export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => { + // The correct messageHash for downward messages can't be reliably obtained + await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete); +}; + export const NETWORKS = { acala: usingAcalaPlaygrounds, astar: usingAstarPlaygrounds, @@ -161,6 +173,17 @@ export class XcmTestHelper { } } + uniqueChainMultilocationForRelay() { + return { + V3: { + parents: 0, + interior: { + X1: {Parachain: this._getNativeId()}, + }, + }, + }; + } + async sendUnqTo( networkName: keyof typeof NETWORKS, randomAccount: IKeyringPair, @@ -470,4 +493,116 @@ export class XcmTestHelper { }); } -} \ No newline at end of file + private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') { + // eslint-disable-next-line require-await + return await usingPlaygrounds(async (helper) => { + const relayForceKV = () => { + const random = Math.random(); + const key = `relay-forced-key (instance: ${random})`; + const val = `relay-forced-value (instance: ${random})`; + const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex(); + + return { + call, + key, + val, + }; + }; + + if(variant == 'plain') { + const kv = relayForceKV(); + return { + program: helper.arrange.makeTransactProgram({ + weightMultiplier: 1, + call: kv.call, + }), + kvs: [kv], + }; + } else { + const kv0 = relayForceKV(); + const kv1 = relayForceKV(); + + const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex(); + return { + program: helper.arrange.makeTransactProgram({ + weightMultiplier: 2, + call: batchCall, + }), + kvs: [kv0, kv1], + }; + } + }); + } + + async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') { + const {program, kvs} = await this._relayXcmTransactSetStorage(variant); + + await usingRelayPlaygrounds(relayUrl, async (helper) => { + await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [ + this.uniqueChainMultilocationForRelay(), + program, + ]); + }); + + await usingPlaygrounds(async (helper) => { + await expectDownwardXcmComplete(helper); + + for(const kv of kvs) { + const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]); + expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val); + } + }); + } + + private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') { + // eslint-disable-next-line require-await + return await usingPlaygrounds(async (helper) => { + const emptyAccount = helper.arrange.createEmptyAccount().address; + + const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex(); + + let call; + + if(variant == 'plain') { + call = forceSetBalanceCall; + + } else if(variant == 'dispatchAs') { + call = helper.constructApiCall('api.tx.utility.dispatchAs', [ + { + system: 'Root', + }, + forceSetBalanceCall, + ]).method.toHex(); + } else { + call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex(); + } + + return { + program: helper.arrange.makeTransactProgram({ + weightMultiplier: 1, + call, + }), + emptyAccount, + }; + }); + } + + async relayIsNotPermittedToSetBalance( + relaySudoer: IKeyringPair, + variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs', + ) { + const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant); + + await usingRelayPlaygrounds(relayUrl, async (helper) => { + await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [ + this.uniqueChainMultilocationForRelay(), + program, + ]); + }); + + await usingPlaygrounds(async (helper) => { + await expectDownwardXcmNoPermission(helper); + expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n); + }); + } +} From 06b395f7b928836522c61c78d7a43c2af7d2842a Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 28 Sep 2023 20:45:03 +0200 Subject: [PATCH 06/19] fix: allow xcm transact for gov bodies when they exist --- runtime/common/config/xcm/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/common/config/xcm/mod.rs b/runtime/common/config/xcm/mod.rs index a825d729ab..d27794ba0d 100644 --- a/runtime/common/config/xcm/mod.rs +++ b/runtime/common/config/xcm/mod.rs @@ -166,8 +166,10 @@ pub struct XcmCallFilter; impl XcmCallFilter { fn allow_gov_and_sys_call(call: &RuntimeCall) -> bool { match call { - RuntimeCall::System(..) - | RuntimeCall::Identity(..) + RuntimeCall::System(..) => true, + + #[cfg(feature = "governance")] + RuntimeCall::Identity(..) | RuntimeCall::Preimage(..) | RuntimeCall::Democracy(..) | RuntimeCall::Council(..) From 668280b1206de9640cc6971283f55bc921a1854d Mon Sep 17 00:00:00 2001 From: PraetorP Date: Fri, 29 Sep 2023 11:32:27 +0000 Subject: [PATCH 07/19] feat(xcm tests): `XcmHelper` integraion for QTZ\UNQ --- .baedeker/.gitignore | 3 +- .github/workflows/xcm.yml | 2 +- tests/src/xcm/lowLevelXcmQuartz.test.ts | 214 +++++---------- tests/src/xcm/lowLevelXcmUnique.test.ts | 335 +----------------------- tests/src/xcm/xcm.types.ts | 47 ++-- tests/src/xcm/xcmQuartz.test.ts | 102 ++++---- 6 files changed, 173 insertions(+), 530 deletions(-) diff --git a/.baedeker/.gitignore b/.baedeker/.gitignore index fb2b1c2276..a0d706661e 100644 --- a/.baedeker/.gitignore +++ b/.baedeker/.gitignore @@ -1,4 +1,5 @@ /.bdk-env -/rewrites.jsonnet +/rewrites*.jsonnet /vendor /baedeker-library +!/rewrites.example.jsonnet \ No newline at end of file diff --git a/.github/workflows/xcm.yml b/.github/workflows/xcm.yml index a70bdbd155..b9598a8d02 100644 --- a/.github/workflows/xcm.yml +++ b/.github/workflows/xcm.yml @@ -38,7 +38,7 @@ jobs: with: matrix: | network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime} - network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime} + network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime} network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime} xcm: diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts index 1c1e52ad22..0bee550650 100644 --- a/tests/src/xcm/lowLevelXcmQuartz.test.ts +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -15,10 +15,9 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util'; -import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; -import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types'; - +import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util'; +import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types'; +import {hexToString} from '@polkadot/util'; const testHelper = new XcmTestHelper('quartz'); @@ -26,24 +25,6 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; - let balanceQuartzTokenInit: bigint; - let balanceQuartzTokenMiddle: bigint; - let balanceQuartzTokenFinal: bigint; - let balanceKaruraTokenInit: bigint; - let balanceKaruraTokenMiddle: bigint; - let balanceKaruraTokenFinal: bigint; - let balanceQuartzForeignTokenInit: bigint; - let balanceQuartzForeignTokenMiddle: bigint; - let balanceQuartzForeignTokenFinal: bigint; - - // computed by a test transfer from prod Quartz to prod Karura. - // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9 - // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block) - const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n; - const karuraEps = 8n * 10n ** 16n; - - let karuraBackwardTransferAmount: bigint; - before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); @@ -72,15 +53,19 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { minimalBalance: 1000000000000000000n, }; - await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) => + hexToString(v.toJSON()['symbol'])) as string[]; + + if(!assets.includes('QTZ')) { + await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + } else { + console.log('QTZ token already registered on Karura assetRegistry pallet'); + } await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); - balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address); - balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); }); await usingPlaygrounds(async (helper) => { - await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT); - balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address); + await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); }); }); @@ -100,67 +85,17 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => { // the the corresponding foreign assets are not registered describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => { let alice: IKeyringPair; - let alith: IKeyringPair; - - const testAmount = 100_000_000_000n; - let quartzParachainJunction; - let quartzAccountJunction; - let quartzParachainMultilocation: any; - let quartzAccountMultilocation: any; - let quartzCombinedMultilocation: any; - - let messageSent: any; - - const maxWaitBlocks = 3; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); - quartzParachainJunction = {Parachain: QUARTZ_CHAIN}; - quartzAccountJunction = { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }; - - quartzParachainMultilocation = { - V2: { - parents: 1, - interior: { - X1: quartzParachainJunction, - }, - }, - }; - - quartzAccountMultilocation = { - V2: { - parents: 0, - interior: { - X1: quartzAccountJunction, - }, - }, - }; - quartzCombinedMultilocation = { - V2: { - parents: 1, - interior: { - X2: [quartzParachainJunction, quartzAccountJunction], - }, - }, - }; // Set the default version to wrap the first message to other chains. await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); - - // eslint-disable-next-line require-await - await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => { - alith = helper.account.alithAccount(); - }); }); itSub('Quartz rejects KAR tokens from Karura', async () => { @@ -195,22 +130,12 @@ describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { minimalBalance: 1n, }; - let balanceQuartzTokenInit: bigint; - let balanceQuartzTokenMiddle: bigint; - let balanceQuartzTokenFinal: bigint; - let balanceForeignQtzTokenInit: bigint; - let balanceForeignQtzTokenMiddle: bigint; - let balanceForeignQtzTokenFinal: bigint; - let balanceMovrTokenInit: bigint; - let balanceMovrTokenMiddle: bigint; - let balanceMovrTokenFinal: bigint; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice); - balanceForeignQtzTokenInit = 0n; // Set the default version to wrap the first message to other chains. await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); @@ -239,20 +164,22 @@ describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { const isSufficient = true; const unitsPerSecond = 1n; const numAssetsWeightHint = 0; - - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: quartzAssetLocation, - metadata: quartzAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); - + if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) { + console.log('Quartz asset already registered on Moonriver'); + } else { + const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ + location: quartzAssetLocation, + metadata: quartzAssetMetadata, + existentialDeposit, + isSufficient, + unitsPerSecond, + numAssetsWeightHint, + }); + + console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); + + await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); + } // >>> Acquire Quartz AssetId Info on Moonriver >>> console.log('Acquire Quartz AssetId Info on Moonriver.......'); @@ -267,13 +194,10 @@ describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n); console.log('Sponsoring random Account.......DONE'); // <<< Sponsoring random Account <<< - - balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address); }); await usingPlaygrounds(async (helper) => { await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT); - balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address); }); }); @@ -296,7 +220,7 @@ describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { let alice: IKeyringPair; - let sender: IKeyringPair; + let randomAccount: IKeyringPair; const QTZ_ASSET_ID_ON_SHIDEN = 1; const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; @@ -304,71 +228,69 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { // Quartz -> Shiden const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? - const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ - const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens - // Shiden -> Quartz - const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ - const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ - let balanceAfterQuartzToShidenXCM: bigint; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { alice = await privateKey('//Alice'); - [sender] = await helper.arrange.createAccounts([100n], alice); - console.log('sender', sender.address); + randomAccount = helper.arrange.createEmptyAccount(); + await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); + console.log('sender: ', randomAccount.address); // Set the default version to wrap the first message to other chains. await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION); }); await usingShidenPlaygrounds(shidenUrl, async (helper) => { - console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production - await helper.assets.create( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - alice.address, - QTZ_MINIMAL_BALANCE_ON_SHIDEN, - ); - - await helper.assets.setMetadata( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - 'Cross chain QTZ', - 'xcQTZ', - Number(QTZ_DECIMALS), - ); - - console.log('2. Register asset location on Shiden'); - const assetLocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, + if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { + console.log('1. Create foreign asset and metadata'); + // TODO update metadata with values from production + await helper.assets.create( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + alice.address, + QTZ_MINIMAL_BALANCE_ON_SHIDEN, + ); + + await helper.assets.setMetadata( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + 'Cross chain QTZ', + 'xcQTZ', + Number(QTZ_DECIMALS), + ); + + console.log('2. Register asset location on Shiden'); + const assetLocation = { + V2: { + parents: 1, + interior: { + X1: { + Parachain: QUARTZ_CHAIN, + }, }, }, - }, - }; - - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); + }; - console.log('3. Set QTZ payment for XCM execution on Shiden'); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); + console.log('3. Set QTZ payment for XCM execution on Shiden'); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + } else { + console.log('QTZ is already registered on Shiden'); + } console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)'); - await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance); + await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance); }); }); itSub('Should connect and send QTZ to Shiden', async () => { - await testHelper.sendUnqTo('shiden', sender); + await testHelper.sendUnqTo('shiden', randomAccount); }); itSub('Should connect to Shiden and send QTZ back', async () => { - await testHelper.sendUnqBack('shiden', alice, sender); + await testHelper.sendUnqBack('shiden', alice, randomAccount); }); itSub('Shiden can send only up to its balance', async () => { diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index ad69cbf828..1cbafd4639 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -16,318 +16,14 @@ import {IKeyringPair} from '@polkadot/types/types'; import config from '../config'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util'; -import {Event} from '../util/playgrounds/unique.dev'; +import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util'; import {nToBigInt} from '@polkadot/util'; import {hexToString} from '@polkadot/util'; -import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types'; - - -const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; -const SENDER_BUDGET = 2n * TRANSFER_AMOUNT; -const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n; -const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT; -const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; - -let balanceUniqueTokenInit: bigint; -let balanceUniqueTokenMiddle: bigint; -let balanceUniqueTokenFinal: bigint; -let unqFees: bigint; +import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types'; const testHelper = new XcmTestHelper('unique'); -async function genericSendUnqTo( - networkName: keyof typeof NETWORKS, - randomAccount: IKeyringPair, - randomAccountOnTargetChain = randomAccount, -) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); - await usingPlaygrounds(async (helper) => { - balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); - const destination = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: mapToChainId(networkName), - }, - }, - }, - }; - - const beneficiary = { - V2: { - parents: 0, - interior: { - X1: ( - networkName == 'moonbeam' ? - { - AccountKey20: { - network: 'Any', - key: randomAccountOnTargetChain.address, - }, - } - : - { - AccountId32: { - network: 'Any', - id: randomAccountOnTargetChain.addressRaw, - }, - } - ), - }, - }, - }; - - const assets = { - V2: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - const feeAssetItem = 0; - - await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); - const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); - - unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees)); - expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; - - await targetPlayground(networkUrl, async (helper) => { - /* - Since only the parachain part of the Polkadex - infrastructure is launched (without their - solochain validators), processing incoming - assets will lead to an error. - This error indicates that the Polkadex chain - received a message from the Unique network, - since the hash is being checked to ensure - it matches what was sent. - */ - if(networkName == 'polkadex') { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash); - } else { - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash); - } - }); - - }); -} - -async function genericSendUnqBack( - networkName: keyof typeof NETWORKS, - sudoer: IKeyringPair, - randomAccountOnUnq: IKeyringPair, -) { - const networkUrl = mapToChainUrl(networkName); - - const targetPlayground = getDevPlayground(networkName); - await usingPlaygrounds(async (helper) => { - - const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - randomAccountOnUnq.addressRaw, - uniqueAssetId, - SENDBACK_AMOUNT, - ); - - let xcmProgramSent: any; - - - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram); - xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); - xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); - - await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash); - - balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address); - - expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN); - - }); -} - -async function genericSendOnlyOwnedBalance( - networkName: keyof typeof NETWORKS, - sudoer: IKeyringPair, -) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); - - const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS); - - await usingPlaygrounds(async (helper) => { - const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName)); - await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance); - const moreThanTargetChainHas = 2n * targetChainBalance; - - const targetAccount = helper.arrange.createEmptyAccount(); - const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - moreThanTargetChainHas, - ); - - let maliciousXcmProgramSent: any; - - - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram); - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); - maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); - - await expectFailedToTransact(helper, maliciousXcmProgramSent); - - const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(targetAccountBalance).to.be.equal(0n); - }); -} - -async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) { - const networkUrl = mapToChainUrl(netwokrName); - const targetPlayground = getDevPlayground(netwokrName); - - await usingPlaygrounds(async (helper) => { - const testAmount = 10_000n * (10n ** UNQ_DECIMALS); - const targetAccount = helper.arrange.createEmptyAccount(); - - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - uniqueAssetId, - testAmount, - ); - - const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited( - targetAccount.addressRaw, - { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - testAmount, - ); - - let maliciousXcmProgramFullIdSent: any; - let maliciousXcmProgramHereIdSent: any; - const maxWaitBlocks = 3; - - // Try to trick Unique using full UNQ identification - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId); - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - // Moonbeam case - else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall); - - maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); - - - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent); - - let accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - - // Try to trick Unique using shortened UNQ identification - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId); - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); - - maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); - - await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent); - - accountBalance = await helper.balance.getSubstrate(targetAccount.address); - expect(accountBalance).to.be.equal(0n); - }); -} - -async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) { - const networkUrl = mapToChainUrl(networkName); - const targetPlayground = getDevPlayground(networkName); - let messageSent: any; - - await usingPlaygrounds(async (helper) => { - const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited( - helper.arrange.createEmptyAccount().addressRaw, - { - Concrete: { - parents: 1, - interior: { - X1: { - Parachain: mapToChainId(networkName), - }, - }, - }, - }, - TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT, - ); - await targetPlayground(networkUrl, async (helper) => { - if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId); - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); - // Needed to bypass the call filter. - const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall); - - messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); - } - }); - await expectFailedToTransact(helper, messageSent); - }); -} describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { @@ -375,24 +71,23 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { await usingPlaygrounds(async (helper) => { await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); - balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); }); }); itSub('Should connect and send UNQ to Acala', async () => { - await genericSendUnqTo('acala', randomAccount); + await testHelper.sendUnqTo('acala', randomAccount); }); itSub('Should connect to Acala and send UNQ back', async () => { - await genericSendUnqBack('acala', alice, randomAccount); + await testHelper.sendUnqBack('acala', alice, randomAccount); }); itSub('Acala can send only up to its balance', async () => { - await genericSendOnlyOwnedBalance('acala', alice); + await testHelper.sendOnlyOwnedBalance('acala', alice); }); itSub('Should not accept reserve transfer of UNQ from Acala', async () => { - await genericReserveTransferUNQfrom('acala', alice); + await testHelper.reserveTransferUNQfrom('acala', alice); }); }); @@ -430,7 +125,6 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { await usingPlaygrounds(async (helper) => { await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET); - balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address); }); }); @@ -467,19 +161,19 @@ describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => }); itSub('Unique rejects ACA tokens from Acala', async () => { - await genericRejectNativeTokensFrom('acala', alice); + await testHelper.rejectNativeTokensFrom('acala', alice); }); itSub('Unique rejects GLMR tokens from Moonbeam', async () => { - await genericRejectNativeTokensFrom('moonbeam', alice); + await testHelper.rejectNativeTokensFrom('moonbeam', alice); }); itSub('Unique rejects ASTR tokens from Astar', async () => { - await genericRejectNativeTokensFrom('astar', alice); + await testHelper.rejectNativeTokensFrom('astar', alice); }); itSub('Unique rejects PDX tokens from Polkadex', async () => { - await genericRejectNativeTokensFrom('polkadex', alice); + await testHelper.rejectNativeTokensFrom('polkadex', alice); }); }); @@ -570,7 +264,6 @@ describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => { await usingPlaygrounds(async (helper) => { await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET); - balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address); }); }); @@ -657,19 +350,19 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { }); itSub('Should connect and send UNQ to Astar', async () => { - await genericSendUnqTo('astar', randomAccount); + await testHelper.sendUnqTo('astar', randomAccount); }); itSub('Should connect to Astar and send UNQ back', async () => { - await genericSendUnqBack('astar', alice, randomAccount); + await testHelper.sendUnqBack('astar', alice, randomAccount); }); itSub('Astar can send only up to its balance', async () => { - await genericSendOnlyOwnedBalance('astar', alice); + await testHelper.sendOnlyOwnedBalance('astar', alice); }); itSub('Should not accept reserve transfer of UNQ from Astar', async () => { - await genericReserveTransferUNQfrom('astar', alice); + await testHelper.reserveTransferUNQfrom('astar', alice); }); }); diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index 5e6bd5bebb..630027b9ae 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -3,7 +3,6 @@ import {hexToString} from '@polkadot/util'; import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; import config from '../config'; -import {blake2AsHex} from '@polkadot/util-crypto'; export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037); export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000); @@ -136,10 +135,10 @@ export function getDevPlayground(name: NetworkNames) { } export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n; -const SENDER_BUDGET = 2n * TRANSFER_AMOUNT; -const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n; -const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT; -const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; +export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT; +export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n; +export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT; +export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n; export class XcmTestHelper { private _balanceUniqueTokenInit: bigint = 0n; @@ -163,6 +162,7 @@ export class XcmTestHelper { return UNIQUE_CHAIN; } } + private _isAddress20FormatFor(network: NetworkNames) { switch (network) { case 'moonbeam': @@ -173,6 +173,19 @@ export class XcmTestHelper { } } + private _runtimeVersionedMultilocation() { + return { + V3: { + parents: 1, + interior: { + X1: { + Parachain: this._getNativeId(), + }, + }, + }, + }; + } + uniqueChainMultilocationForRelay() { return { V3: { @@ -250,8 +263,8 @@ export class XcmTestHelper { this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address); this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT; - console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees)); - expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true; + console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees)); + expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true; await targetPlayground(networkUrl, async (helper) => { /* @@ -302,10 +315,10 @@ export class XcmTestHelper { await targetPlayground(networkUrl, async (helper) => { if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram); + await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram); xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]); + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); @@ -354,10 +367,10 @@ export class XcmTestHelper { await targetPlayground(networkUrl, async (helper) => { if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram); + await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram); maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]); + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall); @@ -413,12 +426,12 @@ export class XcmTestHelper { // Try to trick Unique using full UNQ identification await targetPlayground(networkUrl, async (helper) => { if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId); + await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId); maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } // Moonbeam case else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall); @@ -436,11 +449,11 @@ export class XcmTestHelper { // Try to trick Unique using shortened UNQ identification await targetPlayground(networkUrl, async (helper) => { if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId); + await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId); maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]); + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); @@ -478,10 +491,10 @@ export class XcmTestHelper { ); await targetPlayground(networkUrl, async (helper) => { if('getSudo' in helper) { - await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId); + await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId); messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } else if('fastDemocracy' in helper) { - const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]); + const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall); diff --git a/tests/src/xcm/xcmQuartz.test.ts b/tests/src/xcm/xcmQuartz.test.ts index fe7d1083b1..41b3c0aae6 100644 --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -18,6 +18,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util'; import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev'; import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types'; +import {hexToString} from '@polkadot/util'; @@ -483,7 +484,14 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => { minimalBalance: 1000000000000000000n, }; - await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) => + hexToString(v.toJSON()['symbol'])) as string[]; + + if(!assets.includes('QTZ')) { + await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata); + } else { + console.log('QTZ token already registered on Karura assetRegistry pallet'); + } await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n); balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address); balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0}); @@ -969,19 +977,22 @@ describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => { const unitsPerSecond = 1n; const numAssetsWeightHint = 0; - const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ - location: quartzAssetLocation, - metadata: quartzAssetMetadata, - existentialDeposit, - isSufficient, - unitsPerSecond, - numAssetsWeightHint, - }); - - console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); - - await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); - + if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) { + console.log('Quartz asset already registered on Moonriver'); + } else { + const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({ + location: quartzAssetLocation, + metadata: quartzAssetMetadata, + existentialDeposit, + isSufficient, + unitsPerSecond, + numAssetsWeightHint, + }); + + console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal); + + await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal); + } // >>> Acquire Quartz AssetId Info on Moonriver >>> console.log('Acquire Quartz AssetId Info on Moonriver.......'); @@ -1298,40 +1309,43 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { }); await usingShidenPlaygrounds(shidenUrl, async (helper) => { - console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production - await helper.assets.create( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - alice.address, - QTZ_MINIMAL_BALANCE_ON_SHIDEN, - ); - - await helper.assets.setMetadata( - alice, - QTZ_ASSET_ID_ON_SHIDEN, - 'Cross chain QTZ', - 'xcQTZ', - Number(QTZ_DECIMALS), - ); - - console.log('2. Register asset location on Shiden'); - const assetLocation = { - V2: { - parents: 1, - interior: { - X1: { - Parachain: QUARTZ_CHAIN, + if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { + console.log('1. Create foreign asset and metadata'); + // TODO update metadata with values from production + await helper.assets.create( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + alice.address, + QTZ_MINIMAL_BALANCE_ON_SHIDEN, + ); + + await helper.assets.setMetadata( + alice, + QTZ_ASSET_ID_ON_SHIDEN, + 'Cross chain QTZ', + 'xcQTZ', + Number(QTZ_DECIMALS), + ); + + console.log('2. Register asset location on Shiden'); + const assetLocation = { + V2: { + parents: 1, + interior: { + X1: { + Parachain: QUARTZ_CHAIN, + }, }, }, - }, - }; - - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); + }; - console.log('3. Set QTZ payment for XCM execution on Shiden'); - await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]); + console.log('3. Set QTZ payment for XCM execution on Shiden'); + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + } else { + console.log('QTZ is already registered on Shiden'); + } console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)'); await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance); }); From de0fd484c6ecf011266b9db3d7e02160722ff2e9 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Fri, 29 Sep 2023 18:47:44 +0700 Subject: [PATCH 08/19] fix: cargo clippy --- runtime/common/config/xcm/mod.rs | 6 +++--- vendor/baedeker-library | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 160000 vendor/baedeker-library diff --git a/runtime/common/config/xcm/mod.rs b/runtime/common/config/xcm/mod.rs index d27794ba0d..25eb8f2772 100644 --- a/runtime/common/config/xcm/mod.rs +++ b/runtime/common/config/xcm/mod.rs @@ -185,10 +185,10 @@ impl XcmCallFilter { fn allow_utility_call(call: &RuntimeCall) -> bool { match call { RuntimeCall::Utility(pallet_utility::Call::batch { calls, .. }) => { - calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + calls.iter().all(Self::allow_gov_and_sys_call) } RuntimeCall::Utility(pallet_utility::Call::batch_all { calls, .. }) => { - calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + calls.iter().all(Self::allow_gov_and_sys_call) } RuntimeCall::Utility(pallet_utility::Call::as_derivative { call, .. }) => { Self::allow_gov_and_sys_call(call) @@ -197,7 +197,7 @@ impl XcmCallFilter { Self::allow_gov_and_sys_call(call) } RuntimeCall::Utility(pallet_utility::Call::force_batch { calls, .. }) => { - calls.iter().all(|call| Self::allow_gov_and_sys_call(call)) + calls.iter().all(Self::allow_gov_and_sys_call) } _ => false, } diff --git a/vendor/baedeker-library b/vendor/baedeker-library new file mode 160000 index 0000000000..9f1eca0cea --- /dev/null +++ b/vendor/baedeker-library @@ -0,0 +1 @@ +Subproject commit 9f1eca0cea9f50ce8486f2a4b9db65892ea12c36 From 716b83bf89e3880ecd74ae2a2f07114f5dc8de2c Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 2 Oct 2023 13:32:13 +0200 Subject: [PATCH 09/19] fix: rename makeUnpaidTransactProgram --- tests/src/xcm/xcm.types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index 630027b9ae..de96deb2ea 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -525,7 +525,7 @@ export class XcmTestHelper { if(variant == 'plain') { const kv = relayForceKV(); return { - program: helper.arrange.makeTransactProgram({ + program: helper.arrange.makeUnpaidTransactProgram({ weightMultiplier: 1, call: kv.call, }), @@ -537,7 +537,7 @@ export class XcmTestHelper { const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex(); return { - program: helper.arrange.makeTransactProgram({ + program: helper.arrange.makeUnpaidTransactProgram({ weightMultiplier: 2, call: batchCall, }), @@ -591,7 +591,7 @@ export class XcmTestHelper { } return { - program: helper.arrange.makeTransactProgram({ + program: helper.arrange.makeUnpaidTransactProgram({ weightMultiplier: 1, call, }), From a6308d9e801153b4c27ed3d1288a1b3d8b930ad3 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 2 Oct 2023 13:33:03 +0200 Subject: [PATCH 10/19] fix: rename makeUnpaidSudoTransactProgram --- tests/src/util/playgrounds/unique.dev.ts | 2 +- tests/src/xcm/xcm.types.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/util/playgrounds/unique.dev.ts b/tests/src/util/playgrounds/unique.dev.ts index 10569d9658..57c54e8227 100644 --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -981,7 +981,7 @@ export class ArrangeGroup { }; } - makeTransactProgram(info: {weightMultiplier: number, call: string}) { + makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) { return { V3: [ { diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index de96deb2ea..a5509afab2 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -525,7 +525,7 @@ export class XcmTestHelper { if(variant == 'plain') { const kv = relayForceKV(); return { - program: helper.arrange.makeUnpaidTransactProgram({ + program: helper.arrange.makeUnpaidSudoTransactProgram({ weightMultiplier: 1, call: kv.call, }), @@ -537,7 +537,7 @@ export class XcmTestHelper { const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex(); return { - program: helper.arrange.makeUnpaidTransactProgram({ + program: helper.arrange.makeUnpaidSudoTransactProgram({ weightMultiplier: 2, call: batchCall, }), @@ -591,7 +591,7 @@ export class XcmTestHelper { } return { - program: helper.arrange.makeUnpaidTransactProgram({ + program: helper.arrange.makeUnpaidSudoTransactProgram({ weightMultiplier: 1, call, }), From d4981bc1ee11bc7c8116b883a78503e335bf8162 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 2 Oct 2023 14:01:57 +0200 Subject: [PATCH 11/19] fix: explaning comment about SetTopic relay router --- tests/src/xcm/lowLevelXcmQuartz.test.ts | 2 +- tests/src/xcm/lowLevelXcmUnique.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts index 0bee550650..42c5716897 100644 --- a/tests/src/xcm/lowLevelXcmQuartz.test.ts +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -314,7 +314,7 @@ describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => { // At the moment there is no reliable way // to establish the correspondence between the `ExecutedDownward` event // and the relay's sent message due to `SetTopic` instruction - // containing an unpredictable topic silently added by the relay on the router level. + // containing an unpredictable topic silently added by the relay's messages on the router level. // This changes the message hash on arrival to our chain. // // See: diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index 1cbafd4639..0a8f657b05 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -378,7 +378,7 @@ describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => { // At the moment there is no reliable way // to establish the correspondence between the `ExecutedDownward` event // and the relay's sent message due to `SetTopic` instruction - // containing an unpredictable topic silently added by the relay on the router level. + // containing an unpredictable topic silently added by the relay's messages on the router level. // This changes the message hash on arrival to our chain. // // See: From cefeef50d97a7c7b6c6344947b31c53bcbf4532c Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 2 Oct 2023 14:15:57 +0200 Subject: [PATCH 12/19] fix: make _uniqueChainMultilocationForRelay private --- tests/src/xcm/xcm.types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index a5509afab2..e6eedf8ae7 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -186,7 +186,7 @@ export class XcmTestHelper { }; } - uniqueChainMultilocationForRelay() { + private _uniqueChainMultilocationForRelay() { return { V3: { parents: 0, @@ -552,7 +552,7 @@ export class XcmTestHelper { await usingRelayPlaygrounds(relayUrl, async (helper) => { await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [ - this.uniqueChainMultilocationForRelay(), + this._uniqueChainMultilocationForRelay(), program, ]); }); @@ -608,7 +608,7 @@ export class XcmTestHelper { await usingRelayPlaygrounds(relayUrl, async (helper) => { await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [ - this.uniqueChainMultilocationForRelay(), + this._uniqueChainMultilocationForRelay(), program, ]); }); From 17703e6a9d581279601cc75ee45cc594001bb2d6 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Mon, 2 Oct 2023 14:27:15 +0200 Subject: [PATCH 13/19] fix: set correct shiden/astar values for our chain --- tests/src/util/playgrounds/unique.xcm.ts | 8 ++++---- tests/src/xcm/lowLevelXcmQuartz.test.ts | 13 +++++-------- tests/src/xcm/lowLevelXcmUnique.test.ts | 11 +++++------ tests/src/xcm/xcmQuartz.test.ts | 11 +++++------ tests/src/xcm/xcmUnique.test.ts | 11 +++++------ 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/tests/src/util/playgrounds/unique.xcm.ts b/tests/src/util/playgrounds/unique.xcm.ts index 12d736ae6c..a980c2c99c 100644 --- a/tests/src/util/playgrounds/unique.xcm.ts +++ b/tests/src/util/playgrounds/unique.xcm.ts @@ -240,19 +240,19 @@ export class TokensGroup extends HelperGroup { } export class AssetsGroup extends HelperGroup { - async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) { + async create(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint) { await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true); } - async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) { + async setMetadata(signer: TSigner, assetId: number | bigint, name: string, symbol: string, decimals: number) { await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true); } - async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) { + async mint(signer: TSigner, assetId: number | bigint, beneficiary: string, amount: bigint) { await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true); } - async account(assetId: string | number, address: string) { + async account(assetId: string | number | bigint, address: string) { const accountAsset = ( await this.helper.callRpc('api.query.assets.account', [assetId, address]) ).toJSON()! as any; diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts index 42c5716897..23139e56c9 100644 --- a/tests/src/xcm/lowLevelXcmQuartz.test.ts +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -222,14 +222,12 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; - const QTZ_ASSET_ID_ON_SHIDEN = 1; - const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; + const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden + const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden // Quartz -> Shiden const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden - const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? - - + const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden before(async () => { await usingPlaygrounds(async (helper, privateKey) => { @@ -245,7 +243,6 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { await usingShidenPlaygrounds(shidenUrl, async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production await helper.assets.create( alice, QTZ_ASSET_ID_ON_SHIDEN, @@ -256,8 +253,8 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { await helper.assets.setMetadata( alice, QTZ_ASSET_ID_ON_SHIDEN, - 'Cross chain QTZ', - 'xcQTZ', + 'Quartz', + 'QTZ', Number(QTZ_DECIMALS), ); diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index 0a8f657b05..b36b17dfb9 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -288,12 +288,12 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; - const UNQ_ASSET_ID_ON_ASTAR = 1; - const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; + const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar + const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar // Unique -> Astar const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar. - const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? + const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar before(async () => { await usingPlaygrounds(async (helper, privateKey) => { @@ -309,7 +309,6 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { await usingAstarPlaygrounds(astarUrl, async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) { console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production await helper.assets.create( alice, UNQ_ASSET_ID_ON_ASTAR, @@ -320,8 +319,8 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { await helper.assets.setMetadata( alice, UNQ_ASSET_ID_ON_ASTAR, - 'Cross chain UNQ', - 'xcUNQ', + 'Unique Network', + 'UNQ', Number(UNQ_DECIMALS), ); diff --git a/tests/src/xcm/xcmQuartz.test.ts b/tests/src/xcm/xcmQuartz.test.ts index 41b3c0aae6..92e4310e89 100644 --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -1283,12 +1283,12 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { let alice: IKeyringPair; let sender: IKeyringPair; - const QTZ_ASSET_ID_ON_SHIDEN = 1; - const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; + const QTZ_ASSET_ID_ON_SHIDEN = 18_446_744_073_709_551_633n; // The value is taken from the live Shiden + const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n; // The value is taken from the live Shiden // Quartz -> Shiden const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden - const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? + const unitsPerSecond = 500_451_000_000_000_000_000n; // The value is taken from the live Shiden const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens @@ -1311,7 +1311,6 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { await usingShidenPlaygrounds(shidenUrl, async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) { console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production await helper.assets.create( alice, QTZ_ASSET_ID_ON_SHIDEN, @@ -1322,8 +1321,8 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => { await helper.assets.setMetadata( alice, QTZ_ASSET_ID_ON_SHIDEN, - 'Cross chain QTZ', - 'xcQTZ', + 'Quartz', + 'QTZ', Number(QTZ_DECIMALS), ); diff --git a/tests/src/xcm/xcmUnique.test.ts b/tests/src/xcm/xcmUnique.test.ts index 531d4f519f..b720914581 100644 --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -1511,12 +1511,12 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; - const UNQ_ASSET_ID_ON_ASTAR = 1; - const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; + const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar + const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar // Unique -> Astar const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar. - const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours? + const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens @@ -1539,7 +1539,6 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { await usingAstarPlaygrounds(astarUrl, async (helper) => { if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) { console.log('1. Create foreign asset and metadata'); - // TODO update metadata with values from production await helper.assets.create( alice, UNQ_ASSET_ID_ON_ASTAR, @@ -1550,8 +1549,8 @@ describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { await helper.assets.setMetadata( alice, UNQ_ASSET_ID_ON_ASTAR, - 'Cross chain UNQ', - 'xcUNQ', + 'Unique Network', + 'UNQ', Number(UNQ_DECIMALS), ); From 116f1453edc77ddb9a26915984382079029e9e4d Mon Sep 17 00:00:00 2001 From: Pavel Orlov <45266194+PraetorP@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:36:35 +0000 Subject: [PATCH 14/19] Update tests/src/xcm/xcm.types.ts Co-authored-by: Daniel Shiposha --- tests/src/xcm/xcm.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index e6eedf8ae7..ea4baf402b 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -155,7 +155,7 @@ export class XcmTestHelper { switch (this._nativeRuntime) { case 'opal': // To-Do - return 10; + return 1001; case 'quartz': return QUARTZ_CHAIN; case 'unique': From 0243f65534e44747ff7303a550902b0732be410d Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 2 Oct 2023 13:00:08 +0000 Subject: [PATCH 15/19] refactor(xcm test): rename `reserveTransferUNQfrom` --- tests/src/xcm/lowLevelXcmQuartz.test.ts | 4 ++-- tests/src/xcm/lowLevelXcmUnique.test.ts | 8 ++++---- tests/src/xcm/xcm.types.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/src/xcm/lowLevelXcmQuartz.test.ts b/tests/src/xcm/lowLevelXcmQuartz.test.ts index 23139e56c9..6f06ed760d 100644 --- a/tests/src/xcm/lowLevelXcmQuartz.test.ts +++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts @@ -214,7 +214,7 @@ describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => { }); itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => { - await testHelper.reserveTransferUNQfrom('moonriver', alice); + await testHelper.rejectReserveTransferUNQfrom('moonriver', alice); }); }); @@ -295,7 +295,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => { }); itSub('Should not accept reserve transfer of QTZ from Shiden', async () => { - await testHelper.reserveTransferUNQfrom('shiden', alice); + await testHelper.rejectReserveTransferUNQfrom('shiden', alice); }); }); diff --git a/tests/src/xcm/lowLevelXcmUnique.test.ts b/tests/src/xcm/lowLevelXcmUnique.test.ts index b36b17dfb9..f66432dab1 100644 --- a/tests/src/xcm/lowLevelXcmUnique.test.ts +++ b/tests/src/xcm/lowLevelXcmUnique.test.ts @@ -87,7 +87,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => { }); itSub('Should not accept reserve transfer of UNQ from Acala', async () => { - await testHelper.reserveTransferUNQfrom('acala', alice); + await testHelper.rejectReserveTransferUNQfrom('acala', alice); }); }); @@ -142,7 +142,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => { }); itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => { - await testHelper.reserveTransferUNQfrom('polkadex', alice); + await testHelper.rejectReserveTransferUNQfrom('polkadex', alice); }); }); @@ -280,7 +280,7 @@ describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => { }); itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => { - await testHelper.reserveTransferUNQfrom('moonbeam', alice); + await testHelper.rejectReserveTransferUNQfrom('moonbeam', alice); }); }); @@ -361,7 +361,7 @@ describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => { }); itSub('Should not accept reserve transfer of UNQ from Astar', async () => { - await testHelper.reserveTransferUNQfrom('astar', alice); + await testHelper.rejectReserveTransferUNQfrom('astar', alice); }); }); diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index ea4baf402b..931b9f4985 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -385,7 +385,7 @@ export class XcmTestHelper { }); } - async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) { + async rejectReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) { const networkUrl = mapToChainUrl(netwokrName); const targetPlayground = getDevPlayground(netwokrName); From 78c4f0c72874978b1f77680ff9c3dd59180e18ae Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 2 Oct 2023 13:08:54 +0000 Subject: [PATCH 16/19] fix(test xcm): typos --- tests/src/xcm/xcm.types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/xcm/xcm.types.ts b/tests/src/xcm/xcm.types.ts index 931b9f4985..ae596fd4c9 100644 --- a/tests/src/xcm/xcm.types.ts +++ b/tests/src/xcm/xcm.types.ts @@ -385,9 +385,9 @@ export class XcmTestHelper { }); } - async rejectReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) { - const networkUrl = mapToChainUrl(netwokrName); - const targetPlayground = getDevPlayground(netwokrName); + async rejectReserveTransferUNQfrom(networkName: keyof typeof NETWORKS, sudoer: IKeyringPair) { + const networkUrl = mapToChainUrl(networkName); + const targetPlayground = getDevPlayground(networkName); await usingPlaygrounds(async (helper) => { const testAmount = 10_000n * (10n ** UNQ_DECIMALS); @@ -434,7 +434,7 @@ export class XcmTestHelper { const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall); + await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using path asset identification`,batchCall); maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } @@ -456,7 +456,7 @@ export class XcmTestHelper { const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]); // Needed to bypass the call filter. const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]); - await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); + await helper.fastDemocracy.executeProposal(`${networkName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall); maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent); } From b4e7cb4fb12ab34617d623032aacdcc5ffb35526 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 2 Oct 2023 13:49:09 +0000 Subject: [PATCH 17/19] fix(tests xcm): remove vendor folder --- vendor/baedeker-library | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/baedeker-library diff --git a/vendor/baedeker-library b/vendor/baedeker-library deleted file mode 160000 index 9f1eca0cea..0000000000 --- a/vendor/baedeker-library +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f1eca0cea9f50ce8486f2a4b9db65892ea12c36 From f78c9266d8456b563f4ea3c20c89763bc342086a Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 2 Oct 2023 14:16:17 +0000 Subject: [PATCH 18/19] Revert "fix(tests xcm): remove vendor folder" This reverts commit b4e7cb4fb12ab34617d623032aacdcc5ffb35526. --- vendor/baedeker-library | 1 + 1 file changed, 1 insertion(+) create mode 160000 vendor/baedeker-library diff --git a/vendor/baedeker-library b/vendor/baedeker-library new file mode 160000 index 0000000000..9f1eca0cea --- /dev/null +++ b/vendor/baedeker-library @@ -0,0 +1 @@ +Subproject commit 9f1eca0cea9f50ce8486f2a4b9db65892ea12c36 From 31072020f43ea32bf87136a27655ebe7b721fb0a Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 2 Oct 2023 14:17:01 +0000 Subject: [PATCH 19/19] fix(tests xcm): remove vendor folder --- vendor/baedeker-library | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/baedeker-library diff --git a/vendor/baedeker-library b/vendor/baedeker-library deleted file mode 160000 index 9f1eca0cea..0000000000 --- a/vendor/baedeker-library +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f1eca0cea9f50ce8486f2a4b9db65892ea12c36