diff --git a/test/helpers/assets.ts b/test/helpers/assets.ts index aed15a04b65..ceb5232d8b6 100644 --- a/test/helpers/assets.ts +++ b/test/helpers/assets.ts @@ -25,14 +25,14 @@ export const PARA_1001_SOURCE_LOCATION = { export interface AssetMetadata { name: string; symbol: string; - decimals: BN; + decimals: bigint; isFrozen: boolean; } export const relayAssetMetadata: AssetMetadata = { name: "DOT", symbol: "DOT", - decimals: new BN(12), + decimals: 12n, isFrozen: false, }; diff --git a/test/helpers/xcm.ts b/test/helpers/xcm.ts index d8a4f977ade..03b6405bb58 100644 --- a/test/helpers/xcm.ts +++ b/test/helpers/xcm.ts @@ -101,7 +101,11 @@ export async function registerForeignAsset( context .polkadotJs() .tx.assetManager.setAssetUnitsPerSecond(asset, unitsPerSecond, numAssetsWeightHint!) - ) + ), + { + expectEvents: [context.polkadotJs().events.assetManager.UnitsPerSecondChanged], + allowFailures: false, + } ); // check asset in storage const registeredAsset = ( @@ -202,9 +206,12 @@ export async function injectHrmpMessageAndSeal( interface Junction { Parachain?: number; - AccountId32?: { network: "Any" | XcmV3JunctionNetworkId["type"]; id: Uint8Array | string }; - AccountIndex64?: { network: "Any" | XcmV3JunctionNetworkId["type"]; index: number }; - AccountKey20?: { network: "Any" | XcmV3JunctionNetworkId["type"]; key: Uint8Array | string }; + AccountId32?: { network: "Any" | XcmV3JunctionNetworkId["type"] | null; id: Uint8Array | string }; + AccountIndex64?: { network: "Any" | XcmV3JunctionNetworkId["type"] | null; index: number }; + AccountKey20?: { + network: "Any" | XcmV3JunctionNetworkId["type"] | null; + key: Uint8Array | string; + }; PalletInstance?: number; GeneralIndex?: bigint; GeneralKey?: { length: number; data: Uint8Array }; @@ -344,13 +351,13 @@ export class XcmFragment { } // Add a `DescendOrigin` instruction - descend_origin(): this { + descend_origin(network: "Any" | XcmV3JunctionNetworkId["type"] | null = null): this { if (this.config.descend_origin != null) { this.instructions.push({ DescendOrigin: { X1: { AccountKey20: { - network: "Any", + network, key: this.config.descend_origin, }, }, @@ -460,7 +467,7 @@ export class XcmFragment { /// XCM V3 calls as_v3(): any { return { - V3: replaceNetworkAny(this.instructions), + V3: this.instructions, }; } @@ -554,7 +561,7 @@ export class XcmFragment { ): this { this.instructions.push({ QueryPallet: { - module_name: stringToU8a(module_name), + module_name, response_info: { destination, query_id, @@ -576,8 +583,8 @@ export class XcmFragment { this.instructions.push({ ExpectPallet: { index, - name: stringToU8a(name), - module_name: stringToU8a(module_name), + name, + module_name, crate_major, min_crate_minor, }, @@ -807,27 +814,6 @@ export class XcmFragment { } } -function replaceNetworkAny(obj: AnyObject | Array): any { - if (Array.isArray(obj)) { - return obj.map((item) => replaceNetworkAny(item)); - } else if (typeof obj === "object" && obj !== null) { - const newObj: AnyObject = {}; - for (const key in obj) { - if (key === "network" && obj[key] === "Any") { - newObj[key] = null; - } else { - newObj[key] = replaceNetworkAny(obj[key]); - } - } - return newObj; - } - return obj; -} - -type AnyObject = { - [key: string]: any; -}; - export const registerXcmTransactorAndContract = async (context: DevModeContext) => { await context.createBlock( context diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-1.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-1.ts new file mode 100644 index 00000000000..c5cc78e4ac4 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-1.ts @@ -0,0 +1,80 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3401", + title: "Mock XCM V3 - downward transfer with non-triggered error handler", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure that Alith does not receive 10 dot without error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // But since there is no error, and the deposit is on the error handler, the assets + // will be trapped + .with(function () { + return this.set_error_handler_with([this.deposit_asset]); + }) + .clear_origin() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure ALITH did not reveive anything + const alith_dot_balance = await context + .polkadotJs() + .query.localAssets.account(assetId, alith.address); + + expect(alith_dot_balance.isNone, "Alith's DOT balance is not empty").to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-2.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-2.ts new file mode 100644 index 00000000000..40aa5f26f1b --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-2.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3402", + title: "Mock XCM V3 - downward transfer with triggered error handler", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure that Alith does receive 10 dot because there is error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + .with(function () { + return this.set_error_handler_with([this.deposit_asset]); + }) + .trap() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-3.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-3.ts new file mode 100644 index 00000000000..18aca28c1b1 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-3.ts @@ -0,0 +1,79 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3403", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and without error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // Set an appendix to be executed after the XCM message is executed. No matter if errors + .with(function () { + return this.set_appendix_with([this.deposit_asset]); + }) + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-4.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-4.ts new file mode 100644 index 00000000000..0cf22bb079d --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-4.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3404", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + // The goal is to show appendix runs even if there is an error + .with(function () { + return this.set_appendix_with([this.deposit_asset]); + }) + .trap() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-5.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-5.ts new file mode 100644 index 00000000000..769695550d4 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-5.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3405", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + // The goal is to show appendix runs even if there is an error + .with(function () { + return this.set_appendix_with([this.deposit_asset]); + }) + .trap() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-6.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-6.ts new file mode 100644 index 00000000000..ad5beb4e1d2 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-error-and-appendix-6.ts @@ -0,0 +1,122 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3406", + title: "Mock XCM V3 - downward transfer claim trapped assets", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + + // BuyExecution does not charge for fees because we registered it for not doing so + // But since there is no error, and the deposit is on the error handler, the assets + // will be trapped. + // Goal is to trapp assets, so that later can be claimed + // Since we only BuyExecution, but we do not do anything with the assets after that, + // they are trapped + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + }) + .reserve_asset_deposited() + .buy_execution() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + + // Make sure ALITH did not reveive anything + const alith_dot_balance = await context + .polkadotJs() + .query.localAssets.account(assetId, alith.address); + + expect(alith_dot_balance.isNone).to.be.true; + }); + + it({ + id: "T01", + title: "Should make sure that Alith receives claimed assets", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + // Claim assets that were previously trapped + // assets: the assets that were trapped + // ticket: the version of the assets (xcm version) + .claim_asset() + .buy_execution() + // Deposit assets, this time correctly, on Alith + .deposit_asset() + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-dmp-queue.ts b/test/suites/dev/test-xcm-v2/test-mock-dmp-queue.ts new file mode 100644 index 00000000000..21835580a9d --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-dmp-queue.ts @@ -0,0 +1,156 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; +import { u8aToHex } from "@polkadot/util"; + +import { GLMR } from "@moonwall/util"; +import { XcmFragment, weightMessage } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +describeSuite({ + id: "D3407", + title: "Mock XCMP - test XCMP execution", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "Should test DMP on_initialization and on_idle", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // TODO this test mostly changes it's nature due to proof size accounting + // by now we just decrease the number of supported messages from 50 to 20. + const numMsgs = 20; + // let's target half of then being executed + + // xcmp reserved is BLOCK/4 + const totalDmpWeight = + context.polkadotJs().consts.system.blockWeights.maxBlock.refTime.toBigInt() / 4n; + + // we want half of numParaMsgs to be executed. That give us how much each message weights + const weightPerMessage = (totalDmpWeight * BigInt(2)) / BigInt(numMsgs); + + // Now we need to construct the message. This needs to: + // - pass barrier (withdraw + buyExecution + unlimited buyExecution*n) + // we know at least 2 instructions are needed per message (withdrawAsset + buyExecution) + // how many unlimited buyExecutions do we need to append? + + // we will bias this number. The reason is we want to test the decay, and therefore we need + // an unbalanced number of messages executed. We specifically need that at some point + // we get out of the loop of the execution (we reach the threshold limit), to then + // go on idle + + const config = { + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: 1_000_000_000_000_000n, + }, + ], + }; + + // How much does the withdraw weight? + const withdrawWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).withdraw_asset().as_v2()) + ); + + // How much does the buyExecution weight? + const buyExecutionWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).buy_execution().as_v2()) + ); + + // How much does the refundSurplus weight? + // We use refund surplus because it has 0 pov + // it's easier to focus on reftime + const refundSurplusWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).refund_surplus().as_v2()) + ); + + const refundSurplusPerMessage = + (weightPerMessage - withdrawWeight - buyExecutionWeight) / refundSurplusWeight; + + const xcmMessage = new XcmFragment(config) + .withdraw_asset() + .buy_execution() + .refund_surplus(refundSurplusPerMessage) + .as_v2(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + + // We want these isntructions to fail in BuyExecution. That means + // WithdrawAsset needs to work. The only way for this to work + // is to fund each sovereign account + const sovereignAddress = u8aToHex( + new Uint8Array([...new TextEncoder().encode("Parent")]) + ).padEnd(42, "0"); + + // We first fund the parent sovereign account with 1000 + // we will only withdraw 1, so no problem on this + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, 1n * GLMR), + { allowFailures: false } + ); + + // now we start injecting messages + // several + for (let i = 0; i < numMsgs; i++) { + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + } + + await context.createBlock(); + + const signedBlock = await context.polkadotJs().rpc.chain.getBlock(); + const apiAt = await context.polkadotJs().at(signedBlock.block.header.hash); + const allRecords = await apiAt.query.system.events(); + // lets grab at which point the dmp queue was exhausted + const exhaustIndex = allRecords.findIndex(({ event }) => + context.polkadotJs().events.dmpQueue.MaxMessagesExhausted.is(event) + ); + + expect( + exhaustIndex, + "Index not found where dmpQueue is exhausted" + ).to.be.greaterThanOrEqual(0); + + // OnInitialization + const eventsExecutedOnInitialization = allRecords.filter( + ({ event }, index) => + context.polkadotJs().events.dmpQueue.ExecutedDownward.is(event) && index < exhaustIndex + ); + + // OnIdle + const eventsExecutedOnIdle = allRecords.filter( + ({ event }, index) => + context.polkadotJs().events.dmpQueue.ExecutedDownward.is(event) && index > exhaustIndex + ); + + // the test was designed to go half and half + expect(eventsExecutedOnInitialization.length).to.be.eq(10); + expect(eventsExecutedOnIdle.length).to.be.eq(10); + const pageIndex = await apiAt.query.dmpQueue.pageIndex(); + expect(pageIndex.beginUsed.toBigInt()).to.eq(0n); + expect(pageIndex.endUsed.toBigInt()).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-1.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-1.ts new file mode 100644 index 00000000000..4ab8292ba59 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-1.ts @@ -0,0 +1,100 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3408", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should NOT receive a 10 Statemine tokens to Alith with old prefix", + test: async function () { + // We are going to test that, using the prefix prior to + // https://github.com/paritytech/cumulus/pull/831 + // we cannot receive the tokens on the assetId registed with the old prefix + + // Old prefix: + // Parachain(Statemint parachain) + // GeneralIndex(assetId being transferred) + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { X2: [{ Parachain: statemint_para_id }, { GeneralIndex: 0n }] }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + let alith_dot_balance = await context + .polkadotJs() + .query.assets.account(assetId, alith.address); + + // The message execution failed + expect(alith_dot_balance.isNone).to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-2.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-2.ts new file mode 100644 index 00000000000..8c944575278 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-2.ts @@ -0,0 +1,108 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const FOREIGN_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3409", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive a 10 Statemine tokens to Alith with new prefix", + test: async function () { + // We are going to test that, using the prefix after + // https://github.com/paritytech/cumulus/pull/831 + // we can receive the tokens on the assetId registed with the old prefix + + // New prefix: + // Parachain(Statemint parachain) + // PalletInstance(Statemint assets pallet instance) + // GeneralIndex(assetId being transferred) + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + expect( + (await context.polkadotJs().query.assets.account(assetId, alith.address)) + .unwrap() + .balance.toBigInt() + ).to.eq(10n * FOREIGN_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-3.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-3.ts new file mode 100644 index 00000000000..353698fc283 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-3.ts @@ -0,0 +1,98 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3410", + title: "Mock XCM - receive horizontal transfer of DEV", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let random: KeyringPair; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + transferredBalance = 100000000000000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance) + ); + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should NOT receive MOVR from para Id 2000 with old reanchor", + test: async function () { + const ownParaId = (await context.polkadotJs().query.parachainInfo.parachainId()).toNumber(); + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: balancesPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The message should not have been succesfully executed, since old prefix is not supported + // anymore + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString()).to.eq(transferredBalance.toString()); + + // the random address does not receive anything + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(randomBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-4.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-4.ts new file mode 100644 index 00000000000..cf16776f44a --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-4.ts @@ -0,0 +1,107 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + weightMessage, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3411", + title: "Mock XCM - receive horizontal transfer of DEV with new reanchor", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let random: KeyringPair; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + + transferredBalance = 100000000000000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should receive MOVR from para Id 2000 with new reanchor logic", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + const chargedWeight = await weightMessage( + context, + context.polkadotJs().createType("XcmVersionedXcm", xcmMessage) + ); + // We are charging chargedWeight + // chargedWeight * 50000 = chargedFee + const chargedFee = chargedWeight * 50000n; + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // We should expect sovereign balance to be 0, since we have transferred the full amount + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString()).to.eq(0n.toString()); + + // In the case of the random address: we have transferred 100000000000000, + // but chargedFee have been deducted + // for weight payment + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + const expectedRandomBalance = transferredBalance - chargedFee; + expect(randomBalance).to.eq(expectedRandomBalance); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-5.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-5.ts new file mode 100644 index 00000000000..8a46dabb9cd --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-5.ts @@ -0,0 +1,142 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith, baltathar } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3412", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + // registerAsset + const { result } = await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo( + context + .polkadotJs() + .tx.assetManager.registerLocalAsset( + baltathar.address, + baltathar.address, + true, + new BN(1) + ) + ) + ); + + const eventsRegister = result?.events; + + // Look for assetId in events + const event = eventsRegister!.find(({ event }) => + context.polkadotJs().events.assetManager.LocalAssetRegistered.is(event) + )!; + assetId = event.event.data.assetId.toHex(); + + transferredBalance = 100000000000000n; + + // mint asset + await context.createBlock( + context + .polkadotJs() + .tx.localAssets.mint(assetId, alith.address, transferredBalance) + .signAsync(baltathar) + ); + + sovereignAddress = sovereignAccountOfSibling(context, 2000); + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + // transfer to para Id sovereign to emulate having sent the tokens + await context.createBlock( + context.polkadotJs().tx.localAssets.transfer(assetId, sovereignAddress, transferredBalance), + { allowFailures: false } + ); + }); + + it({ + id: "T01", + title: "Should receive 10 Local Asset tokens and sufficent DEV to pay for fee", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const localAssetsPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "LocalAssets")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + { + multilocation: { + parents: 0, + interior: { + X2: [ + { PalletInstance: localAssetsPalletIndex }, + { GeneralIndex: BigInt(assetId) }, + ], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset(2n) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's LOCAL parachain tokens + const alithLocalTokBalance = ( + await context.polkadotJs().query.localAssets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alithLocalTokBalance.toString()).to.eq(transferredBalance.toString()); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-6.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-6.ts new file mode 100644 index 00000000000..3f123f6c749 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-6.ts @@ -0,0 +1,135 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; + +import { alith } from "@moonwall/util"; + +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const FOREIGN_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; +const STATEMINT_ASSET_ONE_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 1 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3413", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetIdZero: string; + let assetIdOne: string; + + beforeAll(async () => { + // registerForeignAsset 0 + const { registeredAssetId: registeredAssetIdZero, registeredAsset: registeredAssetZero } = + await registerForeignAsset(context, STATEMINT_LOCATION, assetMetadata); + assetIdZero = registeredAssetIdZero; + // registerForeignAsset 1 + const { registeredAssetId: registeredAssetIdOne, registeredAsset: registeredAssetOne } = + await registerForeignAsset(context, STATEMINT_ASSET_ONE_LOCATION, assetMetadata, 0, 1); + assetIdOne = registeredAssetIdOne; + + expect(registeredAssetZero.owner.toHex()).to.eq(palletId.toLowerCase()); + expect(registeredAssetOne.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive 10 asset 0 tokens using statemint asset 1 as fee", + test: async function () { + // We are going to test that, using one of them as fee payment (assetOne), + // we can receive the other + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }, + fungible: 10000000000000n, + }, + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 1n }, + ], + }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution(1) // buy execution with asset at index 1 + .deposit_asset(2n) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const alithAssetZeroBalance = ( + await context.polkadotJs().query.assets.account(assetIdZero, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alithAssetZeroBalance).to.eq(10n * FOREIGN_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-7.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-7.ts new file mode 100644 index 00000000000..be241bdb62f --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-7.ts @@ -0,0 +1,139 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith, baltathar } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3414", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + // registerAsset + const { result } = await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo( + context + .polkadotJs() + .tx.assetManager.registerLocalAsset( + baltathar.address, + baltathar.address, + true, + new BN(1) + ) + ) + ); + + const eventsRegister = result?.events; + + // Look for assetId in events + const event = eventsRegister!.find(({ event }) => + context.polkadotJs().events.assetManager.LocalAssetRegistered.is(event) + )!; + assetId = event.event.data.assetId.toHex(); + + transferredBalance = 100000000000000n; + + // mint asset + await context.createBlock( + context + .polkadotJs() + .tx.localAssets.mint(assetId, alith.address, transferredBalance) + .signAsync(baltathar), + { allowFailures: false } + ); + + sovereignAddress = sovereignAccountOfSibling(context, 2000); + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + // transfer to para Id sovereign to emulate having sent the tokens + await context.createBlock( + context.polkadotJs().tx.localAssets.transfer(assetId, sovereignAddress, transferredBalance), + { allowFailures: false } + ); + }); + + it({ + id: "T01", + title: "Should NOT receive 10 Local Assets and DEV for fee with old reanchor", + test: async function () { + const ownParaId = (await context.polkadotJs().query.parachainInfo.parachainId()).toNumber(); + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const localAssetsPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "LocalAssets")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: balancesPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: localAssetsPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: baltathar.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset(2n) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Old reanchor does not work anymore so no reception of tokens + const baltatharLocalTokBalance = await context + .polkadotJs() + .query.localAssets.account(assetId, baltathar.address); + + expect(baltatharLocalTokBalance.isNone).to.eq(true); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-8.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-8.ts new file mode 100644 index 00000000000..8fffad53cb7 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-asset-transfer-8.ts @@ -0,0 +1,94 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3415", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should not receive 10 asset 0 tokens because fee not supported", + test: async function () { + // We are going to test that, using one of them as fee payment (assetOne), + // we can receive the other + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { X2: [{ Parachain: statemint_para_id }, { GeneralIndex: 0n }] }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset(2n) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const alithAssetZeroBalance = await context + .polkadotJs() + .query.assets.account(assetId, alith.address); + + expect(alithAssetZeroBalance.isNone).to.eq(true); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-1.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-1.ts new file mode 100644 index 00000000000..e57668ba3ea --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-1.ts @@ -0,0 +1,101 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3416", + title: "Mock XCM - receive horizontal transact", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(4000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: new BN(1000000000), + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(transferredBalance / 10n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-2.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-2.ts new file mode 100644 index 00000000000..5cd6531908f --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-2.ts @@ -0,0 +1,102 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3417", + title: "Mock XCM - receive horizontal transact with two Descends", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier only allows one descend origin", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using 2 descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(4000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: new BN(1000000000), + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-3.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-3.ts new file mode 100644 index 00000000000..7260b17cf79 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-3.ts @@ -0,0 +1,100 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3418", + title: "Mock XCM - receive horizontal transact without withdraw", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier does not pass without withdraw", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first but without withdraw + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(4000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: new BN(1000000000), + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-4.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-4.ts new file mode 100644 index 00000000000..37f64f38937 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-4.ts @@ -0,0 +1,100 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3419", + title: "Mock XCM - receive horizontal transact without buy execution", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier blocks without buy execution", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first but without buy execution + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(4000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: new BN(1000000000), + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-1.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-1.ts new file mode 100644 index 00000000000..25947124b18 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-1.ts @@ -0,0 +1,154 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3420", + title: "Mock XCM - receive horizontal transact ETHEREUM (transfer)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 25_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 21_000 gas limit + db read + requireWeightAtMost: 550_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(expectedTransferredAmount); + + // Make sure descend address has been deducted fees once (in xcm-executor) and balance + // has been transfered through evm. + const descendAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(descendAccountBalance)).to.eq( + transferredBalance - expectedTransferredAmountPlusFees + ); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-10.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-10.ts new file mode 100644 index 00000000000..c2181371d05 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-10.ts @@ -0,0 +1,153 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { Abi, encodeFunctionData } from "viem"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3429", + title: "Mock XCM - transact ETHEREUM input size check fails", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("CallForwarder"); + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should fail to call the contract due to BoundedVec restriction", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // Matches the BoundedVec limit in the runtime. + const CALL_INPUT_SIZE_LIMIT = Math.pow(2, 16); + + const xcmTransactions = [ + { + V1: { + gas_limit: 1000000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 127).fill(0)), + ], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: 1000000, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 127).fill(0)), + ], + }), + access_list: null, + }, + }, + ]; + + for (const xcmTransaction of xcmTransactions) { + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction as any); + const transferCallEncoded = transferCall?.method.toHex(); + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(40000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: 30000000000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const block = await context.viem().getBlock({ blockTag: "latest" }); + // Input size is invalid by 1 byte, expect block to not include a transaction. + // That means the pallet-ethereum-xcm couldn't decode the provided input to a BoundedVec. + expect(block.transactions.length).to.be.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-2.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-2.ts new file mode 100644 index 00000000000..ebef3bf0367 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-2.ts @@ -0,0 +1,148 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { Abi, encodeFunctionData } from "viem"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3421", + title: "Mock XCM - receive horizontal transact ETHEREUM (call)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("Incrementor"); + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const xcmTransactions = [ + { + V1: { + gas_limit: 100000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: 100000, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + ]; + + let expectedCalls = 0n; + + for (const xcmTransaction of xcmTransactions) { + expectedCalls++; + + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: new BN(4000000000), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + requireWeightAtMost: 3000000000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const actualCalls = await context.readContract!({ + contractAddress: contractDeployed, + contractName: "Incrementor", + functionName: "count", + }); + + expect(BigInt(actualCalls!.toString())).to.eq(expectedCalls); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-3.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-3.ts new file mode 100644 index 00000000000..f48ffdccdf2 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-3.ts @@ -0,0 +1,239 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { Abi, encodeFunctionData } from "viem"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, + MultiLocation, + registerForeignAsset, + weightMessage, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3422", + title: "Mock XCM - receive horizontal transact ETHEREUM (asset fee)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, + }; + const statemint_para_id = 1001; + const statemint_assets_pallet_instance = 50; + const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + + const ASSET_MULTILOCATION: MultiLocation = { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }; + + const STATEMINT_LOCATION = { + Xcm: ASSET_MULTILOCATION, + }; + + let assetId: string; + let sendingAddress: `0x${string}`; + let descendedAddress: `0x${string}`; + let random: KeyringPair; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + // Gas limit + one db read + const assetsToTransfer = (3_300_000_000n + 25_000_000n) * 2n; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("Incrementor"); + + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendedAddress = descendOriginAddress; + random = generateKeyringPair(); + + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata, + 1_000_000_000_000 + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + + let config = { + assets: [ + { + multilocation: ASSET_MULTILOCATION, + fungible: 0n, + }, + ], + beneficiary: descendOriginAddress, + }; + + // How much will the message weight? + const chargedWeight = await weightMessage( + context, + context + .polkadotJs() + .createType( + "XcmVersionedXcm", + new XcmFragment(config) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2() + ) + ); + + // we modify the config now: + // we send assetsToTransfer plus whatever we will be charged in weight + config.assets[0].fungible = assetsToTransfer + chargedWeight; + + // Construct the real message + const xcmMessage = new XcmFragment(config) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure descended address has the transferred foreign assets (minus the xcm fees). + expect( + (await context.polkadotJs().query.assets.account(assetId, descendedAddress)) + .unwrap() + .balance.toBigInt() + ).to.eq(assetsToTransfer); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + const xcmTransactions = [ + { + V1: { + gas_limit: 100000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: 100000, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + ]; + + let expectedCalls = 0n; + + for (const xcmTransaction of xcmTransactions) { + expectedCalls++; + + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: ASSET_MULTILOCATION, + fungible: assetsToTransfer / 2n, + }, + ], + weight_limit: new BN((assetsToTransfer / 2n).toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 100_000 gas + 1 db read + requireWeightAtMost: 2_525_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const actualCalls = ( + await context.viem().call({ + to: contractDeployed, + data: encodeFunctionData({ abi: contractABI, functionName: "count" }), + }) + ).data; + + expect(BigInt(actualCalls!.toString())).to.eq(expectedCalls); + } + // Make sure descended address went below existential deposit and was killed + expect((await context.polkadotJs().query.assets.account(assetId, descendedAddress)).isNone) + .to.be.true; + // Even if the account does not exist in assets aymore, we still have a nonce 1. Reason is: + // - First transact withdrew 1/2 of assets, nonce was increased to 1. + // - Second transact withdrew the last 1/2 of assets, account was reaped and zeroed. + // - The subsequent evm execution increased the nonce to 1, even without sufficient + // references. + // We can expect this to be the behaviour on any xcm fragment that completely drains an + // account to transact ethereum-xcm after. + let nonce = await context.viem().getTransactionCount({ address: descendedAddress }); + expect(nonce).to.be.eq(1); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-4.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-4.ts new file mode 100644 index 00000000000..55ff95b5f76 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-4.ts @@ -0,0 +1,151 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3423", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund the descend origin derivated address + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy without proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let feeAmount = 0n; + + // Gas limit + 2 db reads + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + feeAmount += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 100_000 gas + 2 db read + requireWeightAtMost: 575_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state for the transfer recipient didn't change + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure the descended address has been deducted fees once (in xcm-executor) but + // transfered nothing. + const descendOriginBalance = await context.viem().getBalance({ address: descendAddress }); + expect(BigInt(descendOriginBalance)).to.eq(transferredBalance - feeAmount); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-5.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-5.ts new file mode 100644 index 00000000000..e3873a4f99b --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-5.ts @@ -0,0 +1,158 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3424", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund the descend origin derivated address + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + + // Add proxy with delay 1 + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 1).signAsync(charleth) + ); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy with non-zero delay proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let feeAmount = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + feeAmount += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 100_000 gas + 2 reads + requireWeightAtMost: 575_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state for the transfer recipient didn't change + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure the descended address has been deducted fees once (in xcm-executor) but + // transfered nothing. + const descendOriginBalance = await context.viem().getBalance({ address: descendAddress }); + expect(BigInt(descendOriginBalance)).to.eq(transferredBalance - feeAmount); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-6.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-6.ts new file mode 100644 index 00000000000..3670c03be4a --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-6.ts @@ -0,0 +1,197 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3425", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let charlethBalance: bigint; + let charlethNonce: number; + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We fund the Delegatee, which will send the xcm and pay fees + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendAddress, transferredBalance), + { allowFailures: false } + ); + + // Ensure funded + const balance_delegatee = ( + await context.polkadotJs().query.system.account(descendAddress) + ).data.free.toBigInt(); + expect(balance_delegatee).to.eq(transferredBalance); + + // Add proxy + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 0).signAsync(charleth) + ); + + // Charleth balance after creating the proxy + charlethBalance = ( + await context.polkadotJs().query.system.account(sendingAddress) + ).data.free.toBigInt(); + + // Charleth nonce + charlethNonce = parseInt( + (await context.polkadotJs().query.system.account(sendingAddress)).nonce.toString() + ); + }); + + it({ + id: "T01", + title: "should succeed to transact_through_proxy with proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 100_000 gas + 2db reads + requireWeightAtMost: 575_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The transfer destination + // Make sure the destination address received the funds + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(expectedTransferredAmount); + + // The EVM caller (proxy delegator) + // Make sure CHARLETH called the evm on behalf DESCENDED, and CHARLETH balance was + // deducted. + const charlethAccountBalance = await context + .viem() + .getBalance({ address: sendingAddress }); + expect(BigInt(charlethAccountBalance)).to.eq(charlethBalance - expectedTransferredAmount); + // Make sure CHARLETH nonce was increased, as EVM caller. + const charlethAccountNonce = await context + .viem() + .getTransactionCount({ address: sendingAddress }); + expect(charlethAccountNonce).to.eq(charlethNonce + 1); + charlethNonce++; + + // The XCM sender (proxy delegatee) + // Make sure derived / descended account paid the xcm fees only. + const derivedAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(derivedAccountBalance)).to.eq( + transferredBalance - (expectedTransferredAmountPlusFees - expectedTransferredAmount) + ); + // Make sure derived / descended account nonce still zero. + const derivedAccountNonce = await context + .viem() + .getTransactionCount({ address: descendAddress }); + expect(derivedAccountNonce).to.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-7.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-7.ts new file mode 100644 index 00000000000..48f89341f75 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-7.ts @@ -0,0 +1,203 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth, alith } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3426", + title: "Mock XCM - transact ETHEREUM (proxy) disabled switch", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let charlethBalance: bigint; + let charlethNonce: number; + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We fund the Delegatee, which will send the xcm and pay fees + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendAddress, transferredBalance), + { allowFailures: false } + ); + + // Ensure funded + const balance_delegatee = ( + await context.polkadotJs().query.system.account(descendAddress) + ).data.free.toBigInt(); + expect(balance_delegatee).to.eq(transferredBalance); + + // Add proxy + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 0).signAsync(charleth) + ); + + // Charleth balance after creating the proxy + charlethBalance = ( + await context.polkadotJs().query.system.account(sendingAddress) + ).data.free.toBigInt(); + + // Charleth nonce + charlethNonce = parseInt( + (await context.polkadotJs().query.system.account(sendingAddress)).nonce.toString() + ); + + // We activate the suspension switch + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy with proxy when disabled", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 100_000 gas + 2db reads + requireWeightAtMost: 575_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The transfer destination + // Make sure the destination address did not receive the funds + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // The EVM caller (proxy delegator) + // Make sure CHARLETH balance was not deducted. + const charlethAccountBalance = await context + .viem() + .getBalance({ address: sendingAddress }); + expect(BigInt(charlethAccountBalance)).to.eq(charlethBalance); + // Make sure CHARLETH nonce did not increase. + const charlethAccountNonce = await context + .viem() + .getTransactionCount({ address: sendingAddress }); + expect(charlethAccountNonce).to.eq(charlethNonce); + + // The XCM sender (proxy delegatee) + // Make sure derived / descended account paid the xcm fees only. + const derivedAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(derivedAccountBalance)).to.eq( + transferredBalance - (expectedTransferredAmountPlusFees - expectedTransferredAmount) + ); + // Make sure derived / descended account nonce still zero. + const derivedAccountNonce = await context + .viem() + .getTransactionCount({ address: descendAddress }); + expect(derivedAccountNonce).to.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-8.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-8.ts new file mode 100644 index 00000000000..35fb98cda7c --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-8.ts @@ -0,0 +1,159 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, alith } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3427", + title: "Mock XCM - transact ETHEREUM (non-proxy) disabled switch", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + + // We activate the suspension switch + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + }); + + it({ + id: "T01", + title: "should receive transact and should not be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const xcmTransactions = [ + { + V1: { + gas_limit: 21000, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: 21000, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 25_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmountPlusFees += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: new BN(targetXcmWeight.toString()), + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originType: "SovereignAccount", + // 21_000 gas limit + db read + requireWeightAtMost: 575_000_000n, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure tokens have not bein transferred + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure descend address has been deducted fees once (in xcm-executor) + const descendAddressBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(descendAddressBalance)).to.eq( + transferredBalance - expectedTransferredAmountPlusFees + ); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-9.ts b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-9.ts new file mode 100644 index 00000000000..28819d8edcd --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-mock-hrmp-transact-ethereum-9.ts @@ -0,0 +1,40 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; + +describeSuite({ + id: "D3428", + title: "Mock XCM - EthereumXcm only disable by root", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "should check suspend ethereum xcm only callable by root", + test: async function () { + let suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should be not suspended by default + expect(suspended.toHuman()).to.be.false; + + // We try to activate without sudo + await context.createBlock( + context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution().signAsync(alith) + ); + suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should not have worked, and should still not be suspended + expect(suspended.toHuman()).to.be.false; + + // Now with sudo + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + + suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should have worked, and should now be suspended + expect(suspended.toHuman()).to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-transfer-two-ERC20.ts b/test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer-two-ERC20.ts similarity index 99% rename from test/suites/dev/test-xcm/test-xcm-erc20-transfer-two-ERC20.ts rename to test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer-two-ERC20.ts index c973907a89c..a9962ece5ba 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-transfer-two-ERC20.ts +++ b/test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer-two-ERC20.ts @@ -13,7 +13,7 @@ import { parseEther } from "ethers"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2703", + id: "D3430", title: "Mock XCM - Send two local ERC20", foundationMethods: "dev", testCases: ({ context, it, log }) => { diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-transfer.ts b/test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer.ts similarity index 99% rename from test/suites/dev/test-xcm/test-xcm-erc20-transfer.ts rename to test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer.ts index c4afaa19d61..8f0e550f05a 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-transfer.ts +++ b/test/suites/dev/test-xcm-v2/test-xcm-erc20-transfer.ts @@ -14,7 +14,7 @@ import { parseEther } from "ethers"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2704", + id: "D3431", title: "Mock XCM - Send local erc20", foundationMethods: "dev", testCases: ({ context, it }) => { diff --git a/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-1.ts b/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-1.ts new file mode 100644 index 00000000000..27fdd03477e --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-1.ts @@ -0,0 +1,101 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + weightMessage, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3432", + title: "XCM Moonbase: version compatibility", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sovereignAddress: string; + let random: KeyringPair; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + transferredBalance = 100000000000000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should execute v2 message", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(8000000000), + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + const chargedWeight = await weightMessage( + context, + context.polkadotJs().createType("XcmVersionedXcm", xcmMessage) + ); + + const chargedFee = chargedWeight * 50000n; + + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString(), "Sovereign account not empty, transfer has failed").to.eq( + 0n.toString() + ); + + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + const expectedRandomBalance = transferredBalance - chargedFee; + expect(randomBalance, "Balance not increased, transfer has failed").to.eq( + expectedRandomBalance + ); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-2.ts b/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-2.ts new file mode 100644 index 00000000000..69026989541 --- /dev/null +++ b/test/suites/dev/test-xcm-v2/test-xcm-ver-conversion-2.ts @@ -0,0 +1,102 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + weightMessage, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3433", + title: "XCM Moonriver: version compatibility", + foundationMethods: "dev", + chainType: "moonriver", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sovereignAddress: string; + let random: KeyringPair; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + transferredBalance = 100000000000000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should execute v2 message", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: new BN(8000000000), + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + const chargedWeight = await weightMessage( + context, + context.polkadotJs().createType("XcmVersionedXcm", xcmMessage) + ); + + const chargedFee = chargedWeight * 50000n; + + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString(), "Sovereign account not empty, transfer has failed").to.eq( + 0n.toString() + ); + + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + const expectedRandomBalance = transferredBalance - chargedFee; + expect(randomBalance, "Balance not increased, transfer has failed").to.eq( + expectedRandomBalance + ); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-asset-transfer.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-asset-transfer.ts new file mode 100644 index 00000000000..29ea442ec5f --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-asset-transfer.ts @@ -0,0 +1,53 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset } from "../../../helpers/xcm.js"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3501", + title: "Mock XCM - receive downward transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive a downward transfer of 10 DOTs to Alith", + test: async function () { + // Send RPC call to inject XCM message + // You can provide a message, but if you don't a downward transfer is the default + await customDevRpcRequest("xcm_injectDownwardMessage", [[]]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance).to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-1.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-1.ts new file mode 100644 index 00000000000..7550f8215fe --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-1.ts @@ -0,0 +1,80 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3502", + title: "Mock XCM V3 - downward transfer with non-triggered error handler", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure that Alith does not receive 10 dot without error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // But since there is no error, and the deposit is on the error handler, the assets + // will be trapped + .with(function () { + return this.set_error_handler_with([this.deposit_asset_v3]); + }) + .clear_origin() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage) as any; + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure ALITH did not reveive anything + const alith_dot_balance = await context + .polkadotJs() + .query.localAssets.account(assetId, alith.address); + + expect(alith_dot_balance.isNone, "Alith's DOT balance is not empty").to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-2.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-2.ts new file mode 100644 index 00000000000..251a3edc441 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-2.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3503", + title: "Mock XCM V3 - downward transfer with triggered error handler", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure that Alith does receive 10 dot because there is error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + .with(function () { + return this.set_error_handler_with([this.deposit_asset_v3]); + }) + .trap() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-3.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-3.ts new file mode 100644 index 00000000000..1e8940627d1 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-3.ts @@ -0,0 +1,79 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3504", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and without error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // Set an appendix to be executed after the XCM message is executed. No matter if errors + .with(function () { + return this.set_appendix_with([this.deposit_asset_v3]); + }) + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-4.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-4.ts new file mode 100644 index 00000000000..14309a656f6 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-4.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3505", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + // The goal is to show appendix runs even if there is an error + .with(function () { + return this.set_appendix_with([this.deposit_asset_v3]); + }) + .trap() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-5.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-5.ts new file mode 100644 index 00000000000..3b2b3292b1a --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-5.ts @@ -0,0 +1,82 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3506", + title: "Mock XCM V3 - downward transfer with always triggered appendix", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should make sure Alith receives 10 dot with appendix and error", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .buy_execution() + // BuyExecution does not charge for fees because we registered it for not doing so + // As a consequence the trapped assets will be entirely credited + // The goal is to show appendix runs even if there is an error + .with(function () { + return this.set_appendix_with([this.deposit_asset_v3]); + }) + .trap() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-6.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-6.ts new file mode 100644 index 00000000000..89d511a9560 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-error-and-appendix-6.ts @@ -0,0 +1,122 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { alith } from "@moonwall/util"; +import { RELAY_SOURCE_LOCATION, relayAssetMetadata } from "../../../helpers/assets.js"; +import { registerForeignAsset, XcmFragment } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +// Twelve decimal places in the moonbase relay chain's token +const RELAY_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + +describeSuite({ + id: "D3507", + title: "Mock XCM V3 - downward transfer claim trapped assets", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + RELAY_SOURCE_LOCATION, + relayAssetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + + // BuyExecution does not charge for fees because we registered it for not doing so + // But since there is no error, and the deposit is on the error handler, the assets + // will be trapped. + // Goal is to trapp assets, so that later can be claimed + // Since we only BuyExecution, but we do not do anything with the assets after that, + // they are trapped + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + }) + .reserve_asset_deposited() + .buy_execution() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + + // Make sure ALITH did not reveive anything + const alith_dot_balance = await context + .polkadotJs() + .query.localAssets.account(assetId, alith.address); + + expect(alith_dot_balance.isNone).to.be.true; + }); + + it({ + id: "T01", + title: "Should make sure that Alith receives claimed assets", + test: async function () { + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + Here: null, + }, + }, + fungible: 10n * RELAY_TOKEN, + }, + ], + beneficiary: alith.address, + }) + // Claim assets that were previously trapped + // assets: the assets that were trapped + // ticket: the version of the assets (xcm version) + .claim_asset() + .buy_execution() + // Deposit assets, this time correctly, on Alith + .deposit_asset_v3() + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage); + + const totalMessage = [...receivedMessage.toU8a()]; + + // Send RPC call to inject XCM message + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + // Make sure the state has ALITH's to DOT tokens + const alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance, "Alith's DOT balance is empty").to.eq(10n * RELAY_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-dmp-queue.ts b/test/suites/dev/test-xcm-v3/test-mock-dmp-queue.ts new file mode 100644 index 00000000000..23e066257fd --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-dmp-queue.ts @@ -0,0 +1,157 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; +import { u8aToHex } from "@polkadot/util"; + +import { GLMR } from "@moonwall/util"; +import { XcmFragment, weightMessage } from "../../../helpers/xcm.js"; +import type { XcmVersionedXcm } from "@polkadot/types/lookup"; + +describeSuite({ + id: "D3508", + title: "Mock XCMP - test XCMP execution", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "Should test DMP on_initialization and on_idle", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // TODO this test mostly changes it's nature due to proof size accounting + // by now we just decrease the number of supported messages from 50 to 20. + const numMsgs = 20; + // let's target half of then being executed + + // xcmp reserved is BLOCK/4 + const totalDmpWeight = + context.polkadotJs().consts.system.blockWeights.maxBlock.refTime.toBigInt() / 4n; + + // we want half of numParaMsgs to be executed. That give us how much each message weights + const weightPerMessage = (totalDmpWeight * BigInt(2)) / BigInt(numMsgs); + + // Now we need to construct the message. This needs to: + // - pass barrier (withdraw + buyExecution + unlimited buyExecution*n) + // we know at least 2 instructions are needed per message (withdrawAsset + buyExecution) + // how many unlimited buyExecutions do we need to append? + + // we will bias this number. The reason is we want to test the decay, and therefore we need + // an unbalanced number of messages executed. We specifically need that at some point + // we get out of the loop of the execution (we reach the threshold limit), to then + // go on idle + + const config = { + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: 1_000_000_000_000_000n, + }, + ], + }; + + // How much does the withdraw weight? + const withdrawWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).withdraw_asset().as_v3()) + ); + + // How much does the buyExecution weight? + const buyExecutionWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).buy_execution().as_v3()) + ); + + // How much does the refundSurplus weight? + // We use refund surplus because it has 0 pov + // it's easier to focus on reftime + const refundSurplusWeight = await weightMessage( + context, + context + .polkadotJs() + .createType("XcmVersionedXcm", new XcmFragment(config).refund_surplus().as_v3()) + ); + + const refundSurplusPerMessage = + (weightPerMessage - withdrawWeight - buyExecutionWeight) / refundSurplusWeight; + + const xcmMessage = new XcmFragment(config) + .withdraw_asset() + .buy_execution() + .refund_surplus(refundSurplusPerMessage) + .as_v3(); + + const receivedMessage: XcmVersionedXcm = context + .polkadotJs() + .createType("XcmVersionedXcm", xcmMessage) as any; + + const totalMessage = [...receivedMessage.toU8a()]; + + // We want these isntructions to fail in BuyExecution. That means + // WithdrawAsset needs to work. The only way for this to work + // is to fund each sovereign account + const sovereignAddress = u8aToHex( + new Uint8Array([...new TextEncoder().encode("Parent")]) + ).padEnd(42, "0"); + + // We first fund the parent sovereign account with 1000 + // we will only withdraw 1, so no problem on this + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, 1n * GLMR), + { allowFailures: false } + ); + + // now we start injecting messages + // several + for (let i = 0; i < numMsgs; i++) { + await customDevRpcRequest("xcm_injectDownwardMessage", [totalMessage]); + } + + await context.createBlock(); + + const signedBlock = await context.polkadotJs().rpc.chain.getBlock(); + const apiAt = await context.polkadotJs().at(signedBlock.block.header.hash); + const allRecords = await apiAt.query.system.events(); + + // lets grab at which point the dmp queue was exhausted + const exhaustIndex = allRecords.findIndex(({ event }) => + context.polkadotJs().events.dmpQueue.MaxMessagesExhausted.is(event) + ); + + expect( + exhaustIndex, + "Index not found where dmpQueue is exhausted" + ).to.be.greaterThanOrEqual(0); + + // OnInitialization + const eventsExecutedOnInitialization = allRecords.filter( + ({ event }, index) => + context.polkadotJs().events.dmpQueue.ExecutedDownward.is(event) && index < exhaustIndex + ); + + // OnIdle + const eventsExecutedOnIdle = allRecords.filter( + ({ event }, index) => + context.polkadotJs().events.dmpQueue.ExecutedDownward.is(event) && index > exhaustIndex + ); + + // the test was designed to go half and half + expect(eventsExecutedOnInitialization.length).to.be.eq(10); + expect(eventsExecutedOnIdle.length).to.be.eq(10); + const pageIndex = await apiAt.query.dmpQueue.pageIndex(); + expect(pageIndex.beginUsed.toBigInt()).to.eq(0n); + expect(pageIndex.endUsed.toBigInt()).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-1.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-1.ts new file mode 100644 index 00000000000..14dfba1b9ed --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-1.ts @@ -0,0 +1,61 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect, customDevRpcRequest } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { PARA_2000_SOURCE_LOCATION } from "../../../helpers/assets.js"; +import { registerForeignAsset } from "../../../helpers/xcm.js"; + +const FOREIGN_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const foreign_para_id = 2000; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; + +describeSuite({ + id: "D3509", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + PARA_2000_SOURCE_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive a horizontal transfer of 10 FOREIGNs to Alith", + test: async function () { + // Send RPC call to inject XCM message + // You can provide a message, but if you don't a horizontal transfer is the default + await customDevRpcRequest("xcm_injectHrmpMessage", [foreign_para_id, []]); + + // Create a block in which the XCM will be executed + await context.createBlock(); + + // Make sure the state has ALITH's foreign parachain tokens + let alith_dot_balance = ( + await context.polkadotJs().query.assets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alith_dot_balance).to.eq(10n * FOREIGN_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-2.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-2.ts new file mode 100644 index 00000000000..45fa7dbf669 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-2.ts @@ -0,0 +1,100 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3510", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should NOT receive a 10 Statemine tokens to Alith with old prefix", + test: async function () { + // We are going to test that, using the prefix prior to + // https://github.com/paritytech/cumulus/pull/831 + // we cannot receive the tokens on the assetId registed with the old prefix + + // Old prefix: + // Parachain(Statemint parachain) + // GeneralIndex(assetId being transferred) + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { X2: [{ Parachain: statemint_para_id }, { GeneralIndex: 0n }] }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: new BN(4000000000), + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset_v3() + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + let alith_dot_balance = await context + .polkadotJs() + .query.assets.account(assetId, alith.address); + + // The message execution failed + expect(alith_dot_balance.isNone).to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-3.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-3.ts new file mode 100644 index 00000000000..646433ae118 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-3.ts @@ -0,0 +1,111 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const FOREIGN_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3511", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive a 10 Statemine tokens to Alith with new prefix", + test: async function () { + // We are going to test that, using the prefix after + // https://github.com/paritytech/cumulus/pull/831 + // we can receive the tokens on the assetId registed with the old prefix + + // New prefix: + // Parachain(Statemint parachain) + // PalletInstance(Statemint assets pallet instance) + // GeneralIndex(assetId being transferred) + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset_v3() + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + expect( + (await context.polkadotJs().query.assets.account(assetId, alith.address)) + .unwrap() + .balance.toBigInt() + ).to.eq(10n * FOREIGN_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-4.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-4.ts new file mode 100644 index 00000000000..158da66535c --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-4.ts @@ -0,0 +1,100 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3512", + title: "Mock XCM - receive horizontal transfer of DEV", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let random: KeyringPair; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + transferredBalance = 100000000000000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance) + ); + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should NOT receive MOVR from para Id 2000 with old reanchor", + test: async function () { + const ownParaId = (await context.polkadotJs().query.parachainInfo.parachainId()).toNumber(); + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: balancesPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset_v3() + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The message should not have been succesfully executed, since old prefix is not supported + // anymore + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString()).to.eq(transferredBalance.toString()); + + // the random address does not receive anything + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(randomBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-5.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-5.ts new file mode 100644 index 00000000000..f49f75a4eb9 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-5.ts @@ -0,0 +1,108 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + weightMessage, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3513", + title: "Mock XCM - receive horizontal transfer of DEV with new reanchor", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let random: KeyringPair; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + random = generateKeyringPair(); + sovereignAddress = sovereignAccountOfSibling(context, 2000); + transferredBalance = 100000000000000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should receive MOVR from para Id 2000 with new reanchor logic", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: random.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset_v3() + .as_v3(); + + const chargedWeight = await weightMessage( + context, + context.polkadotJs().createType("XcmVersionedXcm", xcmMessage) + ); + // We are charging chargedWeight + // chargedWeight * 50000 = chargedFee + const chargedFee = chargedWeight * 50000n; + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // We should expect sovereign balance to be 0, since we have transferred the full amount + const balance = ( + await context.polkadotJs().query.system.account(sovereignAddress) + ).data.free.toBigInt(); + expect(balance.toString()).to.eq(0n.toString()); + + // In the case of the random address: we have transferred 100000000000000, + // but chargedFee have been deducted + // for weight payment + const randomBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + const expectedRandomBalance = transferredBalance - chargedFee; + expect(randomBalance).to.eq(expectedRandomBalance); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-6.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-6.ts new file mode 100644 index 00000000000..59fc94a5e7b --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-6.ts @@ -0,0 +1,145 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith, baltathar } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3514", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + // registerAsset + const { result } = await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo( + context + .polkadotJs() + .tx.assetManager.registerLocalAsset( + baltathar.address, + baltathar.address, + true, + new BN(1) + ) + ) + ); + + const eventsRegister = result?.events; + + // Look for assetId in events + const event = eventsRegister!.find(({ event }) => + context.polkadotJs().events.assetManager.LocalAssetRegistered.is(event) + )!; + assetId = event.event.data.assetId.toHex(); + + transferredBalance = 100000000000000n; + + // mint asset + await context.createBlock( + context + .polkadotJs() + .tx.localAssets.mint(assetId, alith.address, transferredBalance) + .signAsync(baltathar) + ); + + sovereignAddress = sovereignAccountOfSibling(context, 2000); + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + // transfer to para Id sovereign to emulate having sent the tokens + await context.createBlock( + context.polkadotJs().tx.localAssets.transfer(assetId, sovereignAddress, transferredBalance), + { allowFailures: false } + ); + }); + + it({ + id: "T01", + title: "Should receive 10 Local Asset tokens and sufficent DEV to pay for fee", + test: async function () { + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const localAssetsPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "LocalAssets")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance, + }, + { + multilocation: { + parents: 0, + interior: { + X2: [ + { PalletInstance: localAssetsPalletIndex }, + { GeneralIndex: BigInt(assetId) }, + ], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: alith.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset_v3(2n) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's LOCAL parachain tokens + const alithLocalTokBalance = ( + await context.polkadotJs().query.localAssets.account(assetId, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alithLocalTokBalance.toString()).to.eq(transferredBalance.toString()); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-7.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-7.ts new file mode 100644 index 00000000000..89997311885 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-7.ts @@ -0,0 +1,138 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; + +import { alith } from "@moonwall/util"; + +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const FOREIGN_TOKEN = 1_000_000_000_000n; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; +const STATEMINT_ASSET_ONE_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 1 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3515", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetIdZero: string; + let assetIdOne: string; + + beforeAll(async () => { + // registerForeignAsset 0 + const { registeredAssetId: registeredAssetIdZero, registeredAsset: registeredAssetZero } = + await registerForeignAsset(context, STATEMINT_LOCATION, assetMetadata); + assetIdZero = registeredAssetIdZero; + // registerForeignAsset 1 + const { registeredAssetId: registeredAssetIdOne, registeredAsset: registeredAssetOne } = + await registerForeignAsset(context, STATEMINT_ASSET_ONE_LOCATION, assetMetadata, 0, 1); + assetIdOne = registeredAssetIdOne; + + expect(registeredAssetZero.owner.toHex()).to.eq(palletId.toLowerCase()); + expect(registeredAssetOne.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should receive 10 asset 0 tokens using statemint asset 1 as fee", + test: async function () { + // We are going to test that, using one of them as fee payment (assetOne), + // we can receive the other + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }, + fungible: 10000000000000n, + }, + { + multilocation: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 1n }, + ], + }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution(1) // buy execution with asset at index 1 + .deposit_asset_v3(2n) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const alithAssetZeroBalance = ( + await context.polkadotJs().query.assets.account(assetIdZero, alith.address) + ) + .unwrap() + .balance.toBigInt(); + + expect(alithAssetZeroBalance).to.eq(10n * FOREIGN_TOKEN); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-8.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-8.ts new file mode 100644 index 00000000000..369525361c3 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-8.ts @@ -0,0 +1,142 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith, baltathar } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + sovereignAccountOfSibling, +} from "../../../helpers/xcm.js"; + +const foreign_para_id = 2000; + +describeSuite({ + id: "D3516", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + let transferredBalance: bigint; + let sovereignAddress: string; + + beforeAll(async () => { + // registerAsset + const { result } = await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo( + context + .polkadotJs() + .tx.assetManager.registerLocalAsset( + baltathar.address, + baltathar.address, + true, + new BN(1) + ) + ) + ); + + const eventsRegister = result?.events; + + // Look for assetId in events + const event = eventsRegister!.find(({ event }) => + context.polkadotJs().events.assetManager.LocalAssetRegistered.is(event) + )!; + assetId = event.event.data.assetId.toHex(); + + transferredBalance = 100000000000000n; + + // mint asset + await context.createBlock( + context + .polkadotJs() + .tx.localAssets.mint(assetId, alith.address, transferredBalance) + .signAsync(baltathar), + { allowFailures: false } + ); + + sovereignAddress = sovereignAccountOfSibling(context, 2000); + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(sovereignAddress, transferredBalance), + { allowFailures: false } + ); + + // transfer to para Id sovereign to emulate having sent the tokens + await context.createBlock( + context.polkadotJs().tx.localAssets.transfer(assetId, sovereignAddress, transferredBalance), + { allowFailures: false } + ); + }); + + it({ + id: "T01", + title: "Should NOT receive 10 Local Assets and DEV for fee with old reanchor", + test: async function () { + const ownParaId = (await context.polkadotJs().query.parachainInfo.parachainId()).toNumber(); + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const localAssetsPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "LocalAssets")! + .index.toNumber(); + + // We are charging 100_000_000 weight for every XCM instruction + // We are executing 4 instructions + // 100_000_000 * 4 * 50000 = 20000000000000 + // We are charging 20 micro DEV for this operation + // The rest should be going to the deposit account + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: balancesPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + { + multilocation: { + parents: 1, + interior: { + X2: [{ Parachain: ownParaId }, { PalletInstance: localAssetsPalletIndex }], + }, + }, + fungible: transferredBalance, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: baltathar.address, + }) + .withdraw_asset() + .clear_origin() + .buy_execution() + .deposit_asset_v3(2n) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, foreign_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Old reanchor does not work anymore so no reception of tokens + const baltatharLocalTokBalance = await context + .polkadotJs() + .query.localAssets.account(assetId, baltathar.address); + + expect(baltatharLocalTokBalance.isNone).to.eq(true); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-9.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-9.ts new file mode 100644 index 00000000000..765fac671c0 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-asset-transfer-9.ts @@ -0,0 +1,97 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { alith } from "@moonwall/util"; +import { + registerForeignAsset, + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, +} from "../../../helpers/xcm.js"; + +const palletId = "0x6D6f646c617373746d6E67720000000000000000"; +const statemint_para_id = 1001; +const statemint_assets_pallet_instance = 50; + +const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: 12n, + isFrozen: false, +}; +const STATEMINT_LOCATION = { + Xcm: { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0 }, + ], + }, + }, +}; + +describeSuite({ + id: "D3517", + title: "Mock XCM - receive horizontal transfer", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let assetId: string; + + beforeAll(async () => { + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + }); + + it({ + id: "T01", + title: "Should not receive 10 asset 0 tokens because fee not supported", + test: async function () { + // We are going to test that, using one of them as fee payment (assetOne), + // we can receive the other + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 1, + interior: { X2: [{ Parachain: statemint_para_id }, { GeneralIndex: 0n }] }, + }, + fungible: 10000000000000n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 80000n, + } as any, + beneficiary: alith.address, + }) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset_v3(2n) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const alithAssetZeroBalance = await context + .polkadotJs() + .query.assets.account(assetId, alith.address); + + expect(alithAssetZeroBalance.isNone).to.eq(true); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-1.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-1.ts new file mode 100644 index 00000000000..529ad9d4e9e --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-1.ts @@ -0,0 +1,106 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3518", + title: "Mock XCM - receive horizontal transact", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 110000n, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 1000000000n, + proofSize: 80000n, + } as any, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(transferredBalance / 10n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-2.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-2.ts new file mode 100644 index 00000000000..09054762577 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-2.ts @@ -0,0 +1,107 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3519", + title: "Mock XCM - receive horizontal transact with two Descends", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier only allows one descend origin", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using 2 descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 110000n, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 1000000000n, + proofSize: 80000n, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-3.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-3.ts new file mode 100644 index 00000000000..a0e493a07bc --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-3.ts @@ -0,0 +1,105 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3520", + title: "Mock XCM - receive horizontal transact without withdraw", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier does not pass without withdraw", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first but without withdraw + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 110000n, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 1000000000n, + proofSize: 80000n, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-4.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-4.ts new file mode 100644 index 00000000000..8b807dee0e3 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-4.ts @@ -0,0 +1,105 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3521", + title: "Mock XCM - receive horizontal transact without buy execution", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "Should fail to transact because barrier blocks without buy execution", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const transferCall = context + .polkadotJs() + .tx.balances.transfer(random.address, transferredBalance / 10n); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first but without buy execution + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 4000000000n, + proofSize: 110000n, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 1000000000n, + proofSize: 80000n, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure testAccount did not receive, because barrier prevented it + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + + expect(testAccountBalance).to.eq(0n); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-1.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-1.ts new file mode 100644 index 00000000000..7f06e12c636 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-1.ts @@ -0,0 +1,160 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3522", + title: "Mock XCM - receive horizontal transact ETHEREUM (transfer)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 25_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction as any); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 21_000 gas limit + db read + requireWeightAtMost: { + refTime: 550_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(expectedTransferredAmount); + + // Make sure descend address has been deducted fees once (in xcm-executor) and balance + // has been transfered through evm. + const descendAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(descendAccountBalance)).to.eq( + transferredBalance - expectedTransferredAmountPlusFees + ); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-10.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-10.ts new file mode 100644 index 00000000000..9ad856db810 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-10.ts @@ -0,0 +1,161 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { Abi, encodeFunctionData } from "viem"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3531", + title: "Mock XCM - transact ETHEREUM input size check succeeds", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("CallForwarder"); + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should succeed to call the contract", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // Matches the BoundedVec limit in the runtime. + const CALL_INPUT_SIZE_LIMIT = Math.pow(2, 16); + + const GAS_LIMIT = 1000000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 128).fill(0)), + ], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 128).fill(0)), + ], + }), + access_list: null, + }, + }, + ]; + + for (const xcmTransaction of xcmTransactions) { + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 40000000000, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 2, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 30000000000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const block = await context.viem().getBlock({ blockTag: "latest" }); + // Input size is valid - on the limit -, expect block to include a transaction. + // That means the pallet-ethereum-xcm decoded the provided input to a BoundedVec. + expect(block.transactions.length).to.be.eq(1); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-11.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-11.ts new file mode 100644 index 00000000000..607d4d3df39 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-11.ts @@ -0,0 +1,160 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { Abi, encodeFunctionData } from "viem"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3532", + title: "Mock XCM - transact ETHEREUM input size check fails", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("CallForwarder"); + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should fail to call the contract due to BoundedVec restriction", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + // Matches the BoundedVec limit in the runtime. + const CALL_INPUT_SIZE_LIMIT = Math.pow(2, 16); + const GAS_LIMIT = 1000000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 127).fill(0)), + ], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "call", + args: [ + "0x0000000000000000000000000000000000000001", + context + .web3() + .utils.bytesToHex(new Uint8Array(CALL_INPUT_SIZE_LIMIT - 127).fill(0)), + ], + }), + access_list: null, + }, + }, + ]; + + for (const xcmTransaction of xcmTransactions) { + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 40000000000, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 3, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 30000000000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const block = await context.viem().getBlock({ blockTag: "latest" }); + // Input size is invalid by 1 byte, expect block to not include a transaction. + // That means the pallet-ethereum-xcm couldn't decode the provided input to a BoundedVec. + expect(block.transactions.length).to.be.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-12.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-12.ts new file mode 100644 index 00000000000..c3cd6eeddcb --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-12.ts @@ -0,0 +1,157 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { GAS_LIMIT_POV_RATIO, generateKeyringPair } from "@moonwall/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3533", + title: "Mock XCM - receive horizontal transact ETHEREUM (transfer)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should receive transact and should use less weight than gas limit", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + + const GAS_LIMIT = 500_000; + + // We will put a very high gas limit. However, the weight accounted + // for the block should only + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 500_000n * 25000n + 25_000_000n + 800000000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 2, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 500_000 gas limit + db read + requireWeightAtMost: { + refTime: 12_525_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state has ALITH's foreign parachain tokens + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(expectedTransferredAmount); + + // Make sure ALITH has been deducted fees once (in xcm-executor) and balance has been + // transfered through evm. + const alithAccountBalance = await context.viem().getBalance({ address: descendAddress }); + expect(BigInt(alithAccountBalance)).to.eq( + transferredBalance - expectedTransferredAmountPlusFees + ); + + const weightBlock = await context.polkadotJs().query.system.blockWeight(); + // Make sure the system block weight corresponds to gas used and not gas limit + // It should be sufficient to verify that we used less than what was marked + expect( + 12_500_000_000n + 25_000_000n - weightBlock.mandatory.refTime.toBigInt() + ).toBeGreaterThan(0n); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-2.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-2.ts new file mode 100644 index 00000000000..4153d5aaea4 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-2.ts @@ -0,0 +1,157 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { Abi, encodeFunctionData } from "viem"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +import { GAS_LIMIT_POV_RATIO } from "@moonwall/util"; + +describeSuite({ + id: "D3523", + title: "Mock XCM - receive horizontal transact ETHEREUM (call)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("Incrementor"); + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const GAS_LIMIT = 100_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + ]; + + let expectedCalls = 0n; + + for (const xcmTransaction of xcmTransactions) { + expectedCalls++; + + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: transferredBalance / 2n, + }, + ], + weight_limit: { + refTime: 4000000000, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 3, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + requireWeightAtMost: { + refTime: 3000000000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const actualCalls = await context.readContract!({ + contractAddress: contractDeployed, + contractName: "Incrementor", + functionName: "count", + }); + + expect(BigInt(actualCalls!.toString())).to.eq(expectedCalls); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-3.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-3.ts new file mode 100644 index 00000000000..3fcf3682458 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-3.ts @@ -0,0 +1,246 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { BN } from "@polkadot/util"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { Abi, encodeFunctionData } from "viem"; +import { generateKeyringPair, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, + MultiLocation, + registerForeignAsset, + weightMessage, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3524", + title: "Mock XCM - receive horizontal transact ETHEREUM (asset fee)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + const assetMetadata = { + name: "FOREIGN", + symbol: "FOREIGN", + decimals: new BN(12), + isFrozen: false, + }; + const statemint_para_id = 1001; + const statemint_assets_pallet_instance = 50; + const palletId = "0x6D6f646c617373746d6E67720000000000000000"; + + const ASSET_MULTILOCATION: MultiLocation = { + parents: 1, + interior: { + X3: [ + { Parachain: statemint_para_id }, + { PalletInstance: statemint_assets_pallet_instance }, + { GeneralIndex: 0n }, + ], + }, + }; + + const STATEMINT_LOCATION = { + Xcm: ASSET_MULTILOCATION, + }; + + let assetId: string; + let sendingAddress: `0x${string}`; + let descendedAddress: `0x${string}`; + let random: KeyringPair; + let contractDeployed: `0x${string}`; + let contractABI: Abi; + + // Gas limit + one db read + const assetsToTransfer = (3_300_000_000n + 25_000_000n) * 2n; + + beforeAll(async () => { + const { contractAddress, abi } = await context.deployContract!("Incrementor"); + + contractDeployed = contractAddress; + contractABI = abi; + + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendedAddress = descendOriginAddress; + random = generateKeyringPair(); + + // registerForeignAsset + const { registeredAssetId, registeredAsset } = await registerForeignAsset( + context, + STATEMINT_LOCATION, + assetMetadata, + 1_000_000_000_000 + ); + assetId = registeredAssetId; + expect(registeredAsset.owner.toHex()).to.eq(palletId.toLowerCase()); + + let config = { + assets: [ + { + multilocation: ASSET_MULTILOCATION, + fungible: 0n, + }, + ], + beneficiary: descendOriginAddress, + }; + + // How much will the message weight? + const chargedWeight = await weightMessage( + context, + context + .polkadotJs() + .createType( + "XcmVersionedXcm", + new XcmFragment(config) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2() + ) as any + ); + + // we modify the config now: + // we send assetsToTransfer plus whatever we will be charged in weight + config.assets[0].fungible = assetsToTransfer + chargedWeight; + + // Construct the real message + const xcmMessage = new XcmFragment(config) + .reserve_asset_deposited() + .clear_origin() + .buy_execution() + .deposit_asset() + .as_v2(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, statemint_para_id, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure descended address has the transferred foreign assets (minus the xcm fees). + expect( + (await context.polkadotJs().query.assets.account(assetId, descendedAddress)) + .unwrap() + .balance.toBigInt() + ).to.eq(assetsToTransfer); + }); + + it({ + id: "T01", + title: "should receive transact and should be able to execute", + test: async function () { + const GAS_LIMIT = 100_000; + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: contractDeployed, + }, + value: 0n, + input: encodeFunctionData({ + abi: contractABI, + functionName: "incr", + args: [], + }), + access_list: null, + }, + }, + ]; + + let expectedCalls = 0n; + + for (const xcmTransaction of xcmTransactions) { + expectedCalls++; + + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: ASSET_MULTILOCATION, + fungible: assetsToTransfer / 2n, + }, + ], + weight_limit: { + refTime: assetsToTransfer / 2n, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 3, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 100_000 gas + 1 db read + requireWeightAtMost: { + refTime: 2_525_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + const actualCalls = ( + await context.viem().call({ + to: contractDeployed, + data: encodeFunctionData({ abi: contractABI, functionName: "count" }), + }) + ).data; + + expect(BigInt(actualCalls!.toString())).to.eq(expectedCalls); + } + // Make sure descended address went below existential deposit and was killed + expect((await context.polkadotJs().query.assets.account(assetId, descendedAddress)).isNone) + .to.be.true; + // Even if the account does not exist in assets aymore, we still have a nonce 1. Reason is: + // - First transact withdrew 1/2 of assets, nonce was increased to 1. + // - Second transact withdrew the last 1/2 of assets, account was reaped and zeroed. + // - The subsequent evm execution increased the nonce to 1, even without sufficient + // references. + // We can expect this to be the behaviour on any xcm fragment that completely drains an + // account to transact ethereum-xcm after. + let nonce = await context.viem().getTransactionCount({ address: descendedAddress }); + expect(nonce).to.be.eq(1); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-4.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-4.ts new file mode 100644 index 00000000000..cf68f02ce8f --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-4.ts @@ -0,0 +1,157 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3525", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund the descend origin derivated address + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy without proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let feeAmount = 0n; + + // Gas limit + 2 db reads + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + feeAmount += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 100_000 gas + 2 db read + requireWeightAtMost: { + refTime: 575_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state for the transfer recipient didn't change + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure the descended address has been deducted fees once (in xcm-executor) but + // transfered nothing. + const descendOriginBalance = await context.viem().getBalance({ address: descendAddress }); + expect(BigInt(descendOriginBalance)).to.eq(transferredBalance - feeAmount); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-5.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-5.ts new file mode 100644 index 00000000000..9cb83a2887a --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-5.ts @@ -0,0 +1,164 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3526", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund the descend origin derivated address + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + + // Add proxy with delay 1 + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 1).signAsync(charleth) + ); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy with non-zero delay proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let feeAmount = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + feeAmount += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 100_000 gas + 2 reads + requireWeightAtMost: { + refTime: 575_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure the state for the transfer recipient didn't change + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure the descended address has been deducted fees once (in xcm-executor) but + // transfered nothing. + const descendOriginBalance = await context.viem().getBalance({ address: descendAddress }); + expect(BigInt(descendOriginBalance)).to.eq(transferredBalance - feeAmount); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-6.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-6.ts new file mode 100644 index 00000000000..d1c9eabdba9 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-6.ts @@ -0,0 +1,203 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3527", + title: "Mock XCM - receive horizontal transact ETHEREUM (proxy)", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let charlethBalance: bigint; + let charlethNonce: number; + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We fund the Delegatee, which will send the xcm and pay fees + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendAddress, transferredBalance), + { allowFailures: false } + ); + + // Ensure funded + const balance_delegatee = ( + await context.polkadotJs().query.system.account(descendAddress) + ).data.free.toBigInt(); + expect(balance_delegatee).to.eq(transferredBalance); + + // Add proxy + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 0).signAsync(charleth) + ); + + // Charleth balance after creating the proxy + charlethBalance = ( + await context.polkadotJs().query.system.account(sendingAddress) + ).data.free.toBigInt(); + + // Charleth nonce + charlethNonce = parseInt( + (await context.polkadotJs().query.system.account(sendingAddress)).nonce.toString() + ); + }); + + it({ + id: "T01", + title: "should succeed to transact_through_proxy with proxy", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 100_000 gas + 2db reads + requireWeightAtMost: { + refTime: 575_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The transfer destination + // Make sure the destination address received the funds + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(expectedTransferredAmount); + + // The EVM caller (proxy delegator) + // Make sure CHARLETH called the evm on behalf DESCENDED, and CHARLETH balance was + // deducted. + const charlethAccountBalance = await context + .viem() + .getBalance({ address: sendingAddress }); + expect(BigInt(charlethAccountBalance)).to.eq(charlethBalance - expectedTransferredAmount); + // Make sure CHARLETH nonce was increased, as EVM caller. + const charlethAccountNonce = await context + .viem() + .getTransactionCount({ address: sendingAddress }); + expect(charlethAccountNonce).to.eq(charlethNonce + 1); + charlethNonce++; + + // The XCM sender (proxy delegatee) + // Make sure derived / descended account paid the xcm fees only. + const derivedAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(derivedAccountBalance)).to.eq( + transferredBalance - (expectedTransferredAmountPlusFees - expectedTransferredAmount) + ); + // Make sure derived / descended account nonce still zero. + const derivedAccountNonce = await context + .viem() + .getTransactionCount({ address: descendAddress }); + expect(derivedAccountNonce).to.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-7.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-7.ts new file mode 100644 index 00000000000..ba25a2143ee --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-7.ts @@ -0,0 +1,209 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, charleth, alith, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3528", + title: "Mock XCM - transact ETHEREUM (proxy) disabled switch", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let charlethBalance: bigint; + let charlethNonce: number; + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20( + context, + charleth.address as `0x${string}` + ); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We fund the Delegatee, which will send the xcm and pay fees + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendAddress, transferredBalance), + { allowFailures: false } + ); + + // Ensure funded + const balance_delegatee = ( + await context.polkadotJs().query.system.account(descendAddress) + ).data.free.toBigInt(); + expect(balance_delegatee).to.eq(transferredBalance); + + // Add proxy + await context.createBlock( + context.polkadotJs().tx.proxy.addProxy(descendAddress, "Any", 0).signAsync(charleth) + ); + + // Charleth balance after creating the proxy + charlethBalance = ( + await context.polkadotJs().query.system.account(sendingAddress) + ).data.free.toBigInt(); + + // Charleth nonce + charlethNonce = parseInt( + (await context.polkadotJs().query.system.account(sendingAddress)).nonce.toString() + ); + + // We activate the suspension switch + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + }); + + it({ + id: "T01", + title: "should fail to transact_through_proxy with proxy when disabled", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmount = 0n; + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 100_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmount += amountToTransfer; + expectedTransferredAmountPlusFees += amountToTransfer + targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context + .polkadotJs() + .tx.ethereumXcm.transactThroughProxy(sendingAddress, xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 100_000 gas + 2db reads + requireWeightAtMost: { + refTime: 575_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // The transfer destination + // Make sure the destination address did not receive the funds + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // The EVM caller (proxy delegator) + // Make sure CHARLETH balance was not deducted. + const charlethAccountBalance = await context + .viem() + .getBalance({ address: sendingAddress }); + expect(BigInt(charlethAccountBalance)).to.eq(charlethBalance); + // Make sure CHARLETH nonce did not increase. + const charlethAccountNonce = await context + .viem() + .getTransactionCount({ address: sendingAddress }); + expect(charlethAccountNonce).to.eq(charlethNonce); + + // The XCM sender (proxy delegatee) + // Make sure derived / descended account paid the xcm fees only. + const derivedAccountBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(derivedAccountBalance)).to.eq( + transferredBalance - (expectedTransferredAmountPlusFees - expectedTransferredAmount) + ); + // Make sure derived / descended account nonce still zero. + const derivedAccountNonce = await context + .viem() + .getTransactionCount({ address: descendAddress }); + expect(derivedAccountNonce).to.eq(0); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-8.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-8.ts new file mode 100644 index 00000000000..9a9f010a2ac --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-8.ts @@ -0,0 +1,165 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { KeyringPair } from "@polkadot/keyring/types"; +import { generateKeyringPair, alith, GAS_LIMIT_POV_RATIO } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessageAndSeal, + descendOriginFromAddress20, +} from "../../../helpers/xcm.js"; + +describeSuite({ + id: "D3529", + title: "Mock XCM - transact ETHEREUM (non-proxy) disabled switch", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let transferredBalance: bigint; + let sendingAddress: `0x${string}`; + let descendAddress: `0x${string}`; + let random: KeyringPair; + + beforeAll(async () => { + const { originAddress, descendOriginAddress } = descendOriginFromAddress20(context); + sendingAddress = originAddress; + descendAddress = descendOriginAddress; + random = generateKeyringPair(); + transferredBalance = 10_000_000_000_000_000_000n; + + // We first fund parachain 2000 sovreign account + await context.createBlock( + context.polkadotJs().tx.balances.transfer(descendOriginAddress, transferredBalance), + { allowFailures: false } + ); + + const balance = ( + await context.polkadotJs().query.system.account(descendOriginAddress) + ).data.free.toBigInt(); + expect(balance).to.eq(transferredBalance); + + // We activate the suspension switch + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + }); + + it({ + id: "T01", + title: "should receive transact and should not be able to execute", + test: async function () { + // Get Pallet balances index + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() == "Balances")! + .index.toNumber(); + + const amountToTransfer = transferredBalance / 10n; + const GAS_LIMIT = 21_000; + + const xcmTransactions = [ + { + V1: { + gas_limit: GAS_LIMIT, + fee_payment: { + Auto: { + Low: null, + }, + }, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + { + V2: { + gas_limit: GAS_LIMIT, + action: { + Call: random.address, + }, + value: amountToTransfer, + input: [], + access_list: null, + }, + }, + ]; + + let expectedTransferredAmountPlusFees = 0n; + + const targetXcmWeight = 1_325_000_000n + 25_000_000n; + const targetXcmFee = targetXcmWeight * 50_000n; + + for (const xcmTransaction of xcmTransactions) { + expectedTransferredAmountPlusFees += targetXcmFee; + // TODO need to update lookup types for xcm ethereum transaction V2 + const transferCall = context.polkadotJs().tx.ethereumXcm.transact(xcmTransaction); + const transferCallEncoded = transferCall?.method.toHex(); + + // We are going to test that we can receive a transact operation from parachain 1 + // using descendOrigin first + const xcmMessage = new XcmFragment({ + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: targetXcmFee, + }, + ], + weight_limit: { + refTime: targetXcmWeight, + proofSize: (GAS_LIMIT / GAS_LIMIT_POV_RATIO) * 7, + } as any, + descend_origin: sendingAddress, + }) + .descend_origin() + .withdraw_asset() + .buy_execution() + .push_any({ + Transact: { + originKind: "SovereignAccount", + // 21_000 gas limit + db read + requireWeightAtMost: { + refTime: 575_000_000, + proofSize: GAS_LIMIT / GAS_LIMIT_POV_RATIO, + }, + call: { + encoded: transferCallEncoded, + }, + }, + }) + .as_v3(); + + // Send an XCM and create block to execute it + await injectHrmpMessageAndSeal(context, 1, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + + // Make sure tokens have not bein transferred + const testAccountBalance = ( + await context.polkadotJs().query.system.account(random.address) + ).data.free.toBigInt(); + expect(testAccountBalance).to.eq(0n); + + // Make sure descend address has been deducted fees once (in xcm-executor) + const descendAddressBalance = await context + .viem() + .getBalance({ address: descendAddress }); + expect(BigInt(descendAddressBalance)).to.eq( + transferredBalance - expectedTransferredAmountPlusFees + ); + } + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-9.ts b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-9.ts new file mode 100644 index 00000000000..6cfb94f0498 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-mock-hrmp-transact-ethereum-9.ts @@ -0,0 +1,40 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; + +describeSuite({ + id: "D3530", + title: "Mock XCM - EthereumXcm only disable by root", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "should check suspend ethereum xcm only callable by root", + test: async function () { + let suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should be not suspended by default + expect(suspended.toHuman()).to.be.false; + + // We try to activate without sudo + await context.createBlock( + context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution().signAsync(alith) + ); + suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should not have worked, and should still not be suspended + expect(suspended.toHuman()).to.be.false; + + // Now with sudo + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.ethereumXcm.suspendEthereumXcmExecution()) + .signAsync(alith) + ); + + suspended = await context.polkadotJs().query.ethereumXcm.ethereumXcmSuspended(); + // should have worked, and should now be suspended + expect(suspended.toHuman()).to.be.true; + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-excess-gas.ts b/test/suites/dev/test-xcm-v3/test-xcm-erc20-excess-gas.ts similarity index 99% rename from test/suites/dev/test-xcm/test-xcm-erc20-excess-gas.ts rename to test/suites/dev/test-xcm-v3/test-xcm-erc20-excess-gas.ts index 414fb51d00c..58288fefe97 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-excess-gas.ts +++ b/test/suites/dev/test-xcm-v3/test-xcm-erc20-excess-gas.ts @@ -14,7 +14,7 @@ import { parseEther } from "ethers"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2701", + id: "D3534", title: "Mock XCM - Test bad contract with excess gas usage", foundationMethods: "dev", testCases: ({ context, it }) => { @@ -140,7 +140,7 @@ describeSuite({ }, { AccountKey20: { - network: "Any", + network: null, key: contractAddress, }, }, diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-fees-and-trap.ts b/test/suites/dev/test-xcm-v3/test-xcm-erc20-fees-and-trap.ts similarity index 95% rename from test/suites/dev/test-xcm/test-xcm-erc20-fees-and-trap.ts rename to test/suites/dev/test-xcm-v3/test-xcm-erc20-fees-and-trap.ts index b0f82bcafb5..f4be2c47ee2 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-fees-and-trap.ts +++ b/test/suites/dev/test-xcm-v3/test-xcm-erc20-fees-and-trap.ts @@ -10,13 +10,12 @@ import { weightMessage, } from "../../../helpers/xcm.js"; import { ALITH_ADDRESS, CHARLETH_ADDRESS, alith } from "@moonwall/util"; -import { stringToU8a } from "@polkadot/util"; import { parseEther } from "ethers"; export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2702", + id: "D3535", title: "Mock XCM - Fails trying to pay fees with ERC20", foundationMethods: "dev", testCases: ({ context, it, log }) => { @@ -83,8 +82,8 @@ describeSuite({ }, { AccountKey20: { - network: "Any", - key: stringToU8a(erc20ContractAddress), + network: null, + key: erc20ContractAddress, }, }, ], @@ -101,8 +100,8 @@ describeSuite({ .withdraw_asset() .clear_origin() .buy_execution() - .deposit_asset(2n) - .as_v2(); + .deposit_asset_v3(2n) + .as_v3(); // Mock the reception of the xcm message await injectHrmpMessageAndSeal(context, paraId, { @@ -190,8 +189,8 @@ describeSuite({ }, { AccountKey20: { - network: "Any", - key: stringToU8a(erc20ContractAddress), + network: null, + key: erc20ContractAddress, }, }, ], @@ -209,7 +208,7 @@ describeSuite({ .withdraw_asset() .clear_origin() .buy_execution() - .as_v2(); + .as_v3(); // Mock the reception of the xcm message await injectHrmpMessageAndSeal(context, paraId, { @@ -244,8 +243,8 @@ describeSuite({ const xcmMessageToClaimAssets = new XcmFragment(claimConfig) .claim_asset() .buy_execution() - .deposit_asset() - .as_v2(); + .deposit_asset_v3() + .as_v3(); const balanceBefore = ( await polkadotJs.query.system.account(paraSovereign) @@ -297,8 +296,8 @@ describeSuite({ }, { AccountKey20: { - network: "Any", - key: stringToU8a(erc20ContractAddress), + network: null, + key: erc20ContractAddress, }, }, ], @@ -314,8 +313,8 @@ describeSuite({ const xcmMessageFailedClaim = new XcmFragment(failedClaimConfig) .claim_asset() .buy_execution() - .deposit_asset() - .as_v2(); + .deposit_asset_v3() + .as_v3(); await injectHrmpMessageAndSeal(context, paraId, { type: "XcmVersionedXcm", diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-v3-filter.ts b/test/suites/dev/test-xcm-v3/test-xcm-erc20-v3-filter.ts similarity index 98% rename from test/suites/dev/test-xcm/test-xcm-erc20-v3-filter.ts rename to test/suites/dev/test-xcm-v3/test-xcm-erc20-v3-filter.ts index e2208c4e67b..d88e6bb1880 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-v3-filter.ts +++ b/test/suites/dev/test-xcm-v3/test-xcm-erc20-v3-filter.ts @@ -14,7 +14,7 @@ import { export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2710", + id: "D3536", title: "Mock XCM V3 - XCM Weight Limit", foundationMethods: "dev", testCases: ({ context, it }) => { @@ -94,7 +94,7 @@ describeSuite({ }, { AccountKey20: { - network: "Any", + network: null, key: erc20ContractAddress, }, }, diff --git a/test/suites/dev/test-xcm/test-xcm-erc20-v3.ts b/test/suites/dev/test-xcm-v3/test-xcm-erc20-v3.ts similarity index 98% rename from test/suites/dev/test-xcm/test-xcm-erc20-v3.ts rename to test/suites/dev/test-xcm-v3/test-xcm-erc20-v3.ts index a288ad4e9e8..f9e68b7488e 100644 --- a/test/suites/dev/test-xcm/test-xcm-erc20-v3.ts +++ b/test/suites/dev/test-xcm-v3/test-xcm-erc20-v3.ts @@ -13,7 +13,7 @@ import { export const ERC20_TOTAL_SUPPLY = 1_000_000_000n; describeSuite({ - id: "D2705", + id: "D3537", title: "Mock XCM V3 - Receive erc20 via XCM", foundationMethods: "dev", testCases: ({ context, it }) => { @@ -93,7 +93,7 @@ describeSuite({ }, { AccountKey20: { - network: "Any", + network: null, key: erc20ContractAddress, }, }, diff --git a/test/suites/dev/test-xcm-v3/test-xcm-transactor-1.ts b/test/suites/dev/test-xcm-v3/test-xcm-transactor-1.ts new file mode 100644 index 00000000000..f738419a655 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-xcm-transactor-1.ts @@ -0,0 +1,28 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; +import { alith } from "@moonwall/util"; + +describeSuite({ + id: "D3538", + title: "Precompiles - xcm transactor", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + beforeAll(async () => { + // register index 0 for Alith + await context.createBlock( + context + .polkadotJs() + .tx.sudo.sudo(context.polkadotJs().tx.xcmTransactor.register(alith.address, 0)) + ); + }); + + it({ + id: "T01", + title: "allows to retrieve index through precompiles", + test: async function () { + const resp = await context.polkadotJs().query.xcmTransactor.indexToAccount(0); + expect(resp.toString()).to.eq(alith.address); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-xcm-transactor-2.ts b/test/suites/dev/test-xcm-v3/test-xcm-transactor-2.ts new file mode 100644 index 00000000000..150c1aa6571 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-xcm-transactor-2.ts @@ -0,0 +1,60 @@ +import "@moonbeam-network/api-augment"; +import { describeSuite, expect, dispatchAsGeneralAdmin } from "@moonwall/cli"; + +describeSuite({ + id: "D3539", + title: "Precompiles - xcm transactor", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "Moonbase: GeneralAdmin should be able to dispatch hrmpManage", + test: async function () { + // Just build the arguments. They dont matter that much though, since + // we will not make sure it executes in the relay + const transactWeights = context + .polkadotJs() + .createType("PalletXcmTransactorTransactWeights", { + transactRequiredWeightAtMost: { refTime: 10000, proofSize: 10000 }, + overallWeight: { refTime: 10000, proofSize: 10000 }, + }); + + let fee = context.polkadotJs().createType("PalletXcmTransactorCurrencyPayment", { + currency: { + AsMultiLocation: { + V3: { + parents: 1, + interior: { + Here: null, + }, + }, + }, + }, + feeAmount: 10000, + }); + + // send HrmpManage + await dispatchAsGeneralAdmin( + context, + (context.polkadotJs().tx.xcmTransactor as any).hrmpManage( + { + Accept: { + para_id: 2000, + }, + }, + fee, + transactWeights + ) + ); + + // Filter for HrmpManagementSent events + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmTransactor.HrmpManagementSent.is(event) + ); + + // It executed! + expect(events).to.have.lengthOf(1); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-xcmv3-max-weight-instructions.ts b/test/suites/dev/test-xcm-v3/test-xcmv3-max-weight-instructions.ts new file mode 100644 index 00000000000..24b85ab39f3 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-xcmv3-max-weight-instructions.ts @@ -0,0 +1,236 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { alith, CHARLETH_ADDRESS } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessage, + sovereignAccountOfSibling, + XcmFragmentConfig, +} from "../../../helpers/xcm.js"; +import { parseEther } from "ethers"; + +describeSuite({ + id: "D3540", + title: "XCM V3 - Max Weight Instructions", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let dotAsset: XcmFragmentConfig; + let amount: bigint; + const paraId: number = 888; + + beforeAll(async () => { + const paraSovereign = sovereignAccountOfSibling(context, paraId); + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() === "Balances")! + .index.toNumber(); + + // Send some native tokens to the sovereign account of paraId (to pay fees) + await context + .polkadotJs() + .tx.balances.transfer(paraSovereign, parseEther("1")) + .signAndSend(alith); + await context.createBlock(); + + amount = 1_000_000_000_000_000n; + dotAsset = { + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: amount, + }, + ], + beneficiary: CHARLETH_ADDRESS, + }; + }); + + it({ + id: "T01", + title: "Should not execute UniversalOrigin", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .universal_origin({ Parachain: paraId }) + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T02", + title: "Should not execute ExportMessage", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .export_message() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T03", + title: "Should not execute LockAsset", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .lock_asset() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T04", + title: "Should not execute UnlockAsset", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .unlock_asset() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T05", + title: "Should not execute NoteUnlockable", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .note_unlockable() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T06", + title: "Should not execute RequestUnlock", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .request_unlock() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + + it({ + id: "T07", + title: "Should not execute AliasOrigin", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .alias_origin() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for WeightNotComputable error + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("WeightNotComputable"); + }, + }); + }, +}); diff --git a/test/suites/dev/test-xcm-v3/test-xcmv3-new-instructions.ts b/test/suites/dev/test-xcm-v3/test-xcmv3-new-instructions.ts new file mode 100644 index 00000000000..99374b587e3 --- /dev/null +++ b/test/suites/dev/test-xcm-v3/test-xcmv3-new-instructions.ts @@ -0,0 +1,416 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, expect } from "@moonwall/cli"; + +import { alith, CHARLETH_ADDRESS } from "@moonwall/util"; +import { + XcmFragment, + RawXcmMessage, + injectHrmpMessage, + sovereignAccountOfSibling, + XcmFragmentConfig, +} from "../../../helpers/xcm.js"; +import { parseEther } from "ethers"; + +// Here we are testing each allowed instruction to be executed. Even if some of them throw an error, +// the important thing (and what we are testing) is that they are +// executed and are not blocked with 'WeightNotComputable' due to using max weight. +describeSuite({ + id: "D3541", + title: "XCM V3 - Max Weight Instructions", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let dotAsset: XcmFragmentConfig; + let amount: bigint; + const paraId: number = 888; + + beforeAll(async () => { + const paraSovereign = sovereignAccountOfSibling(context, paraId); + const metadata = await context.polkadotJs().rpc.state.getMetadata(); + const balancesPalletIndex = metadata.asLatest.pallets + .find(({ name }) => name.toString() === "Balances")! + .index.toNumber(); + + // Send some native tokens to the sovereign account of paraId (to pay fees) + await context + .polkadotJs() + .tx.balances.transfer(paraSovereign, parseEther("1")) + .signAndSend(alith); + await context.createBlock(); + + amount = 1_000_000_000_000_000n; + dotAsset = { + assets: [ + { + multilocation: { + parents: 0, + interior: { + X1: { PalletInstance: balancesPalletIndex }, + }, + }, + fungible: amount, + }, + ], + beneficiary: CHARLETH_ADDRESS, + }; + }); + + it({ + id: "T01", + title: "Should execute BurnAsset", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .burn_asset() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T02", + title: "Should execute ClearTopic", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .clear_topic() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T03", + title: "Should execute ExpectTransactStatus", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .expect_transact_status() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T04", + title: "Should execute ClearTransactStatus", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .clear_transact_status() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T05", + title: "Should execute SetFeesMode", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .set_fees_mode() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T06", + title: "Should execute SetTopic", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + // SetTopic expects an array of 32 bytes + .set_topic() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Success.is(event) + ); + expect(events).to.have.lengthOf(1); + }, + }); + + it({ + id: "T07", + title: "Should execute ReportHolding (Transport)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .report_holding() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("Transport"); + }, + }); + + it({ + id: "T08", + title: "Should execute ExpectAsset (ExpectationFalse)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .expect_asset() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("ExpectationFalse"); + }, + }); + + it({ + id: "T09", + title: "Should execute ExpectOrigin (ExpectationFalse)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .expect_origin() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("ExpectationFalse"); + }, + }); + + it({ + id: "T10", + title: "Should execute ExpectError (ExpectationFalse)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .expect_error() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("ExpectationFalse"); + }, + }); + + it({ + id: "T11", + title: "Should execute QueryPallet (Transport)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .query_pallet() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("Transport"); + }, + }); + + it({ + id: "T12", + title: "Should execute ExpectPallet (NameMismatch)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .expect_pallet() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("NameMismatch"); + }, + }); + + it({ + id: "T13", + title: "Should execute ReportTransactStatus (Transport)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .report_transact_status() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("Transport"); + }, + }); + + it({ + id: "T14", + title: "Should execute UnpaidExecution (BadOrigin)", + test: async function () { + const xcmMessage = new XcmFragment(dotAsset) + .withdraw_asset() + .buy_execution() + .unpaid_execution() + .as_v3(); + + // Mock the reception of the xcm message + await injectHrmpMessage(context, paraId, { + type: "XcmVersionedXcm", + payload: xcmMessage, + } as RawXcmMessage); + await context.createBlock(); + + // Search for Success + const events = (await context.polkadotJs().query.system.events()).filter(({ event }) => + context.polkadotJs().events.xcmpQueue.Fail.is(event) + ); + expect(events).to.have.lengthOf(1); + expect(events[0].event.data[1].toString()).equals("BadOrigin"); + }, + }); + }, +});