From a322ae9edd8f2f439cc9db7873899d1baee0ad08 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Sun, 19 Jan 2025 14:49:53 +0000 Subject: [PATCH] Allow assets managed by AssetManager and EvmForeignAssets to use the pallet-xcm precompile (#3136) * update AssetIdToLocationManager to support both EvmForeignAssets and AssetManager as asset managers * test pallet xcm precompile with old foreign assets approach * fix test --- runtime/moonbase/src/precompiles.rs | 15 +- runtime/moonbeam/src/precompiles.rs | 9 +- runtime/moonriver/src/precompiles.rs | 9 +- .../test-precompile-pallet-xcm-2.ts | 2 - ...recompile-pallet-xcm-with-asset-manager.ts | 418 ++++++++++++++++++ .../test-precompile-pallet-xcm.ts | 4 - 6 files changed, 441 insertions(+), 16 deletions(-) create mode 100644 test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-with-asset-manager.ts diff --git a/runtime/moonbase/src/precompiles.rs b/runtime/moonbase/src/precompiles.rs index 9f23ba2167..b5f454860f 100644 --- a/runtime/moonbase/src/precompiles.rs +++ b/runtime/moonbase/src/precompiles.rs @@ -16,10 +16,13 @@ use super::moonbase_weights; use crate::{ - asset_config::ForeignAssetInstance, xcm_config::XcmExecutorConfig, OpenTechCommitteeInstance, - TreasuryCouncilInstance, + asset_config::ForeignAssetInstance, + xcm_config::{AssetType, XcmExecutorConfig}, + OpenTechCommitteeInstance, TreasuryCouncilInstance, +}; +use crate::{ + AccountId, AssetId, AssetManager, Balances, Erc20XcmBridge, EvmForeignAssets, Runtime, H160, }; -use crate::{AccountId, AssetId, Balances, Erc20XcmBridge, EvmForeignAssets, Runtime, H160}; use frame_support::parameter_types; use moonkit_xcm_primitives::{ location_matcher::{Erc20PalletMatcher, ForeignAssetMatcher, SingleAddressMatcher}, @@ -60,6 +63,7 @@ use pallet_precompile_benchmarks::WeightInfo; use precompile_foreign_asset_migrator::ForeignAssetMigratorPrecompile; use precompile_utils::precompile_set::*; use sp_std::prelude::*; +use xcm_primitives::AsAssetType; parameter_types! { pub P256VerifyWeight: frame_support::weights::Weight = @@ -111,7 +115,10 @@ type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, Callab // Pallet-xcm precompile types. // Type that converts AssetId into Location -type AssetIdToLocationManager = EvmForeignAssets; +type AssetIdToLocationManager = ( + AsAssetType, + EvmForeignAssets, +); // The pallet-balances address is identified by ERC20_BALANCES_PRECOMPILE const type SingleAddressMatch = SingleAddressMatcher; diff --git a/runtime/moonbeam/src/precompiles.rs b/runtime/moonbeam/src/precompiles.rs index 355e905975..226775d798 100644 --- a/runtime/moonbeam/src/precompiles.rs +++ b/runtime/moonbeam/src/precompiles.rs @@ -18,8 +18,8 @@ use super::moonbeam_weights; use crate::{ asset_config::ForeignAssetInstance, xcm_config::{AssetType, XcmExecutorConfig}, - AccountId, AssetId, AssetManager, Balances, Erc20XcmBridge, OpenTechCommitteeInstance, Runtime, - TreasuryCouncilInstance, H160, + AccountId, AssetId, AssetManager, Balances, Erc20XcmBridge, EvmForeignAssets, + OpenTechCommitteeInstance, Runtime, TreasuryCouncilInstance, H160, }; use frame_support::parameter_types; use moonkit_xcm_primitives::{ @@ -111,7 +111,10 @@ type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, Callab // Pallet-xcm precompile types. // Type that converts AssetId into Location -type AssetIdToLocationManager = AsAssetType; +type AssetIdToLocationManager = ( + AsAssetType, + EvmForeignAssets, +); // The pallet-balances address is identified by ERC20_BALANCES_PRECOMPILE const type SingleAddressMatch = SingleAddressMatcher; diff --git a/runtime/moonriver/src/precompiles.rs b/runtime/moonriver/src/precompiles.rs index 662c429801..7dc55968b7 100644 --- a/runtime/moonriver/src/precompiles.rs +++ b/runtime/moonriver/src/precompiles.rs @@ -18,8 +18,8 @@ use super::moonriver_weights; use crate::{ asset_config::ForeignAssetInstance, xcm_config::{AssetType, XcmExecutorConfig}, - AccountId, AssetId, AssetManager, Balances, Erc20XcmBridge, OpenTechCommitteeInstance, Runtime, - TreasuryCouncilInstance, + AccountId, AssetId, AssetManager, Balances, Erc20XcmBridge, EvmForeignAssets, + OpenTechCommitteeInstance, Runtime, TreasuryCouncilInstance, }; use frame_support::parameter_types; use moonkit_xcm_primitives::location_matcher::{ @@ -105,7 +105,10 @@ type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, Callab // Pallet-xcm precompile types. // Type that converts AssetId into Location -type AssetIdToLocationManager = AsAssetType; +type AssetIdToLocationManager = ( + AsAssetType, + EvmForeignAssets, +); // The pallet-balances address is identified by ERC20_BALANCES_PRECOMPILE const type SingleAddressMatch = SingleAddressMatcher; diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-2.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-2.ts index 298870d041..2a533346fb 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-2.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-2.ts @@ -20,8 +20,6 @@ import { PARA_1000_SOURCE_LOCATION, mockHrmpChannelExistanceTx, ARBITRARY_ASSET_ID, - mockAssetBalance, - registerForeignAsset, relayAssetMetadata, RELAY_SOURCE_LOCATION_V4, registerAndFundAsset, diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-with-asset-manager.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-with-asset-manager.ts new file mode 100644 index 0000000000..31d66cbc1b --- /dev/null +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm-with-asset-manager.ts @@ -0,0 +1,418 @@ +import "@moonbeam-network/api-augment"; +import { beforeAll, describeSuite, fetchCompiledContract, expect } from "@moonwall/cli"; +import { ALITH_ADDRESS, BALTATHAR_ADDRESS, alith, createEthersTransaction } from "@moonwall/util"; +import type { u128 } from "@polkadot/types-codec"; +import { numberToHex } from "@polkadot/util"; +import type { PalletAssetsAssetAccount, PalletAssetsAssetDetails } from "@polkadot/types/lookup"; +import { encodeFunctionData } from "viem"; +import { expectEVMResult, mockOldAssetBalance } from "../../../../helpers"; + +const PRECOMPILE_PALLET_XCM_ADDRESS: `0x${string}` = "0x000000000000000000000000000000000000081A"; + +describeSuite({ + id: "D012902", + title: "Precompiles - PalletXcm", + foundationMethods: "dev", + testCases: ({ context, it }) => { + let assetId: u128; + const ADDRESS_ERC20 = "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080"; + const ASSET_ID = 42259045809535163221576417993425387648n; + const amountToSend = 100n; + + beforeAll(async () => { + const balance = 200000000000000n; + const assetBalance: PalletAssetsAssetAccount = context + .polkadotJs() + .createType("PalletAssetsAssetAccount", { + balance: balance, + }); + assetId = context.polkadotJs().createType("u128", ASSET_ID); + + const assetDetails: PalletAssetsAssetDetails = context + .polkadotJs() + .createType("PalletAssetsAssetDetails", { + supply: balance, + }); + + await mockOldAssetBalance( + context, + assetBalance, + assetDetails, + alith, + assetId, + ALITH_ADDRESS, + true + ); + }); + + it({ + id: "T01", + title: "allows to call transferAssetsLocation function", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const dest: [number, any[]] = [1, []]; + + const destinationAddress = + "0101010101010101010101010101010101010101010101010101010101010101"; + const destinationNetworkId = "00"; + const beneficiary: [number, any[]] = [ + 0, + // junction: AccountId32 enum (01) + the 32 byte account + Any network selector(00) + ["0x01" + destinationAddress + destinationNetworkId], + ]; + + const assetLocation: [number, any[]] = [1, []]; + const assetLocationInfo = [[assetLocation, amountToSend]]; + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [dest, beneficiary, assetLocationInfo, 0], + functionName: "transferAssetsLocation", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T02", + title: "allows to call transferAssetsToPara20 function", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const paraId = 1000n; + const assetAddressInfo = [[ADDRESS_ERC20, amountToSend]]; + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [paraId, BALTATHAR_ADDRESS, assetAddressInfo, 0], + functionName: "transferAssetsToPara20", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T03", + title: "allows to call transferAssetsToPara32 function", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const paraId = 1000n; + const assetAddressInfo = [[ADDRESS_ERC20, amountToSend]]; + const beneficiaryAddress = "01010101010101010101010101010101"; + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [paraId, beneficiaryAddress, assetAddressInfo, 0], + functionName: "transferAssetsToPara32", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T04", + title: "allows to call transferAssetsToRelay function", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const assetAddressInfo = [[ADDRESS_ERC20, amountToSend]]; + const beneficiaryAddress = "01010101010101010101010101010101"; + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [beneficiaryAddress, assetAddressInfo, 0], + functionName: "transferAssetsToRelay", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T05", + title: "allows to call transferAssetsUsingTypeAndThenLocation::8425d893 selector", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const dest: [number, any[]] = [1, []]; + const assetLocation: [number, any[]] = [1, []]; + const assetLocationInfo = [[assetLocation, amountToSend]]; + + // DestinationReserve + const assetsAndFeesTransferType = 2; + + const message = { + V3: [ + { + ClearOrigin: null, + }, + ], + }; + const xcmOnDest = context.polkadotJs().createType("XcmVersionedXcm", message); + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [ + dest, + assetLocationInfo, + assetsAndFeesTransferType, + 0n, + assetsAndFeesTransferType, + xcmOnDest.toHex(), + ], + functionName: "transferAssetsUsingTypeAndThenLocation", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T06", + title: "allows to call transferAssetsUsingTypeAndThenLocation::fc19376c selector", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const paraIdInHex = numberToHex(2000, 32); + const parachain_enum_selector = "0x00"; + + // This represents X2(Parent, Parachain(2000)) + const dest: [number, any[]] = [1, [parachain_enum_selector + paraIdInHex.slice(2)]]; + + const remoteReserve: [number, any[]] = [1, []]; + const assetLocation: [number, any[]] = [1, []]; + const assetLocationInfo = [[assetLocation, amountToSend]]; + + const message = { + V3: [ + { + ClearOrigin: null, + }, + ], + }; + const xcmOnDest = context.polkadotJs().createType("XcmVersionedXcm", message); + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [dest, assetLocationInfo, 0n, xcmOnDest.toHex(), remoteReserve], + functionName: "transferAssetsUsingTypeAndThenLocation", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T07", + title: "allows to call transferAssetsUsingTypeAndThenAddress::998093ee selector", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + // Relay as destination + const dest: [number, any[]] = [1, []]; + const assetAddressInfo = [[ADDRESS_ERC20, amountToSend]]; + + // DestinationReserve + const assetsAndFeesTransferType = 2; + + const message = { + V3: [ + { + ClearOrigin: null, + }, + ], + }; + const xcmOnDest = context.polkadotJs().createType("XcmVersionedXcm", message); + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [ + dest, + assetAddressInfo, + assetsAndFeesTransferType, + 0n, + assetsAndFeesTransferType, + xcmOnDest.toHex(), + ], + functionName: "transferAssetsUsingTypeAndThenAddress", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + + it({ + id: "T08", + title: "allows to call transferAssetsUsingTypeAndThenAddress::aaecfc62 selector", + test: async function () { + const { abi: xcmInterface } = fetchCompiledContract("XCM"); + const assetBalanceBefore = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + + const paraIdInHex = numberToHex(2000, 32); + const parachain_enum_selector = "0x00"; + + // This represents X2(Parent, Parachain(2000)) + const dest: [number, any[]] = [1, [parachain_enum_selector + paraIdInHex.slice(2)]]; + const assetAddressInfo = [[ADDRESS_ERC20, amountToSend]]; + const remoteReserve: [number, any[]] = [1, []]; + + const message = { + V3: [ + { + ClearOrigin: null, + }, + ], + }; + const xcmOnDest = context.polkadotJs().createType("XcmVersionedXcm", message); + + const rawTxn = await createEthersTransaction(context, { + to: PRECOMPILE_PALLET_XCM_ADDRESS, + data: encodeFunctionData({ + abi: xcmInterface, + args: [dest, assetAddressInfo, 0n, xcmOnDest.toHex(), remoteReserve], + functionName: "transferAssetsUsingTypeAndThenAddress", + }), + gasLimit: 500_000n, + }); + + const result = await context.createBlock(rawTxn); + expectEVMResult(result.result!.events, "Succeed"); + + const assetBalanceAfter = ( + await context.polkadotJs().query.assets.account(assetId.toU8a(), ALITH_ADDRESS) + ) + .unwrap() + .balance.toBigInt(); + expect(assetBalanceAfter).to.equal(assetBalanceBefore - amountToSend); + }, + }); + }, +}); diff --git a/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm.ts b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm.ts index 750d12e144..faac1e57c2 100644 --- a/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm.ts +++ b/test/suites/dev/moonbase/test-precompile/test-precompile-pallet-xcm.ts @@ -5,16 +5,12 @@ import { numberToHex } from "@polkadot/util"; import { encodeFunctionData, erc20Abi } from "viem"; import { expectEVMResult, - mockAssetBalance, - registerForeignAsset, relayAssetMetadata, ARBITRARY_ASSET_ID, RELAY_SOURCE_LOCATION_V4, registerAndFundAsset, - PARA_1000_SOURCE_LOCATION, } from "../../../../helpers"; import { ethers } from "ethers"; -import { para1000AssetMetadata } from "./test-precompile-pallet-xcm-2"; const PRECOMPILE_PALLET_XCM_ADDRESS: `0x${string}` = "0x000000000000000000000000000000000000081A"; describeSuite({