diff --git a/Cargo.lock b/Cargo.lock index 52b21565a5..3b84060e49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5892,8 +5892,10 @@ dependencies = [ "pallet-conviction-voting", "pallet-crowdloan-rewards", "pallet-democracy", + "pallet-erc20-xcm-bridge", "pallet-ethereum", "pallet-ethereum-chain-id", + "pallet-ethereum-xcm", "pallet-evm", "pallet-evm-precompile-author-mapping", "pallet-evm-precompile-balances-erc20", @@ -6241,8 +6243,10 @@ dependencies = [ "pallet-conviction-voting", "pallet-crowdloan-rewards", "pallet-democracy", + "pallet-erc20-xcm-bridge", "pallet-ethereum", "pallet-ethereum-chain-id", + "pallet-ethereum-xcm", "pallet-evm", "pallet-evm-precompile-author-mapping", "pallet-evm-precompile-balances-erc20", diff --git a/pallets/erc20-xcm-bridge/src/lib.rs b/pallets/erc20-xcm-bridge/src/lib.rs index e15d06e045..9d1ec8546a 100644 --- a/pallets/erc20-xcm-bridge/src/lib.rs +++ b/pallets/erc20-xcm-bridge/src/lib.rs @@ -66,9 +66,10 @@ pub mod pallet { Erc20Matcher::::is_erc20_asset(asset) } pub fn weight_of_erc20_transfer() -> Weight { + let gas_limit = T::Erc20TransferGasLimit::get(); Weight::from_parts( T::Erc20TransferGasLimit::get().saturating_mul(T::WeightPerGas::get().ref_time()), - 0, + gas_limit / 4, // TODO: apply gas/proof_size ratio ) } fn erc20_transfer( diff --git a/runtime/moonbeam/Cargo.toml b/runtime/moonbeam/Cargo.toml index 9f38be91a2..be05ec24c7 100644 --- a/runtime/moonbeam/Cargo.toml +++ b/runtime/moonbeam/Cargo.toml @@ -33,7 +33,9 @@ moonbeam-xcm-benchmarks = { workspace = true } pallet-asset-manager = { workspace = true } pallet-author-mapping = { workspace = true } pallet-crowdloan-rewards = { workspace = true } +pallet-erc20-xcm-bridge = { workspace = true } pallet-ethereum-chain-id = { workspace = true } +pallet-ethereum-xcm = { workspace = true } pallet-maintenance-mode = { workspace = true, features = [ "xcm-support" ] } pallet-migrations = { workspace = true } pallet-moonbeam-orbiters = { workspace = true } @@ -206,7 +208,9 @@ std = [ "pallet-conviction-voting/std", "pallet-crowdloan-rewards/std", "pallet-democracy/std", + "pallet-erc20-xcm-bridge/std", "pallet-ethereum-chain-id/std", + "pallet-ethereum-xcm/std", "pallet-ethereum/std", "pallet-evm-precompile-author-mapping/std", "pallet-evm-precompile-balances-erc20/std", @@ -297,6 +301,7 @@ runtime-benchmarks = [ "pallet-conviction-voting/runtime-benchmarks", "pallet-crowdloan-rewards/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", + "pallet-ethereum-xcm/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-moonbeam-orbiters/runtime-benchmarks", "pallet-parachain-staking/runtime-benchmarks", diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index 31bd494d38..a596b8ee41 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -41,6 +41,7 @@ pub use frame_support::traits::Get; use frame_support::{ construct_runtime, dispatch::{DispatchClass, GetDispatchInfo}, + ensure, pallet_prelude::DispatchResult, parameter_types, traits::{ @@ -79,7 +80,7 @@ use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, IdentityLookup, - PostDispatchInfoOf, UniqueSaturatedInto, + PostDispatchInfoOf, UniqueSaturatedInto, Zero, }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -620,6 +621,32 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type CheckAssociatedRelayNumber = cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; } +pub struct EthereumXcmEnsureProxy; +impl xcm_primitives::EnsureProxy for EthereumXcmEnsureProxy { + fn ensure_ok(delegator: AccountId, delegatee: AccountId) -> Result<(), &'static str> { + // The EVM implicitely contains an Any proxy, so we only allow for "Any" proxies + let def: pallet_proxy::ProxyDefinition = + pallet_proxy::Pallet::::find_proxy( + &delegator, + &delegatee, + Some(ProxyType::Any), + ) + .map_err(|_| "proxy error: expected `ProxyType::Any`")?; + // We only allow to use it for delay zero proxies, as the call will immediatly be executed + ensure!(def.delay.is_zero(), "proxy delay is Non-zero`"); + Ok(()) + } +} + +impl pallet_ethereum_xcm::Config for Runtime { + type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; + type XcmEthereumOrigin = pallet_ethereum_xcm::EnsureXcmEthereumTransaction; + type ReservedXcmpWeight = ReservedXcmpWeight; + type EnsureProxy = EthereumXcmEnsureProxy; + type ControllerOrigin = EnsureRoot; +} + parameter_types! { pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); @@ -999,6 +1026,7 @@ impl Contains for MaintenanceFilter { RuntimeCall::PolkadotXcm(_) => false, RuntimeCall::Treasury(_) => false, RuntimeCall::XcmTransactor(_) => false, + RuntimeCall::EthereumXcm(_) => false, _ => true, } } @@ -1343,6 +1371,9 @@ construct_runtime! { XTokens: orml_xtokens::{Pallet, Call, Storage, Event} = 106, XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Storage, Event} = 107, LocalAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 108, + EthereumXcm: pallet_ethereum_xcm::{Pallet, Call, Storage, Origin} = 109, + Erc20XcmBridge: pallet_erc20_xcm_bridge::{Pallet} = 110, + // Randomness Randomness: pallet_randomness::{Pallet, Call, Storage, Event, Inherent} = 120, diff --git a/runtime/moonbeam/src/xcm_config.rs b/runtime/moonbeam/src/xcm_config.rs index 483d7f2c4a..12184d73b4 100644 --- a/runtime/moonbeam/src/xcm_config.rs +++ b/runtime/moonbeam/src/xcm_config.rs @@ -19,12 +19,15 @@ use super::{ governance, AccountId, AssetId, AssetManager, Assets, Balance, Balances, DealWithFees, - LocalAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, - RuntimeOrigin, Treasury, XcmpQueue, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, + Erc20XcmBridge, LocalAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, }; use pallet_evm_precompileset_assets_erc20::AccountIdAssetIdConversion; -use sp_runtime::traits::Hash as THash; +use sp_runtime::{ + traits::{Hash as THash, PostDispatchInfoOf}, + DispatchErrorWithPostInfo, +}; use frame_support::{ dispatch::Weight, @@ -32,20 +35,20 @@ use frame_support::{ traits::{EitherOfDiverse, Everything, Nothing, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureRoot, RawOrigin}; use sp_core::{ConstU32, H160, H256}; use xcm_builder::{ AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, + CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FungiblesAdapter, NoChecking, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + WeightInfoBounds, }; use xcm::latest::prelude::*; -use xcm_executor::traits::JustTry; +use xcm_executor::traits::{CallDispatcher, JustTry}; use orml_xcm_support::MultiNativeAsset; use xcm_primitives::{ @@ -104,8 +107,21 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // If we receive a MultiLocation of type AccountKey20, just generate a native account AccountKey20Aliases, + // Generate remote accounts according to polkadot standards + xcm_builder::ForeignChainAliasAccount, ); +/// Wrapper type around `LocationToAccountId` to convert an `AccountId` to type `H160`. +pub struct LocationToH160; +impl xcm_executor::traits::Convert for LocationToH160 { + fn convert(location: MultiLocation) -> Result { + >::convert( + location, + ) + .map(Into::into) + } +} + // The non-reserve fungible transactor type // It will use pallet-assets, and the Id will be matched against AsAssetType pub type ForeignFungiblesTransactor = FungiblesAdapter< @@ -207,11 +223,16 @@ parameter_types! { } /// Xcm Weigher shared between multiple Xcm-related configs. -pub type XcmWeigher = FixedWeightBounds; +pub type XcmWeigher = WeightInfoBounds< + moonbeam_xcm_benchmarks::weights::XcmWeight, + RuntimeCall, + MaxInstructions, +>; // Allow paid executions pub type XcmBarrier = ( TakeWeightCredit, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, @@ -254,6 +275,15 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = xcm_primitives::MAX_ASSETS; } +// Our implementation of the Moonbeam Call +// Attachs the right origin in case the call is made to pallet-ethereum-xcm +#[cfg(not(feature = "evm-tracing"))] +moonbeam_runtime_common::impl_moonbeam_xcm_call!(); +#[cfg(feature = "evm-tracing")] +moonbeam_runtime_common::impl_moonbeam_xcm_call_tracing!(); + +moonbeam_runtime_common::impl_evm_runner_precompile_or_eth_xcm!(); + pub struct XcmExecutorConfig; impl xcm_executor::Config for XcmExecutorConfig { type RuntimeCall = RuntimeCall; @@ -286,9 +316,9 @@ impl xcm_executor::Config for XcmExecutorConfig { ); type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type AssetTrap = PolkadotXcm; + type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper; type AssetClaims = PolkadotXcm; - type CallDispatcher = RuntimeCall; + type CallDispatcher = MoonbeamCall; type PalletInstancesInfo = crate::AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); @@ -300,7 +330,10 @@ impl xcm_executor::Config for XcmExecutorConfig { type AssetIsBurnable = Everything; } -type XcmExecutor = xcm_executor::XcmExecutor; +type XcmExecutor = pallet_erc20_xcm_bridge::XcmExecutorWrapper< + RuntimeCall, + xcm_executor::XcmExecutor, +>; // Converts a Signed Local Origin into a MultiLocation pub type LocalOriginToLocation = SignedToAccountId20; @@ -574,6 +607,29 @@ impl pallet_xcm_transactor::Config for Runtime { type HrmpEncoder = moonbeam_relay_encoder::polkadot::PolkadotEncoder; } +parameter_types! { + // This is the relative view of erc20 assets. + // Identified by this prefix + AccountKey20(contractAddress) + // We use the RELATIVE multilocation + pub Erc20XcmBridgePalletLocation: MultiLocation = MultiLocation { + parents:0, + interior: Junctions::X1( + PalletInstance(::index() as u8) + ) + }; + + // To be able to support almost all erc20 implementations, + // we provide a sufficiently hight gas limit. + pub Erc20XcmBridgeTransferGasLimit: u64 = 80_000; +} + +impl pallet_erc20_xcm_bridge::Config for Runtime { + type AccountIdConverter = LocationToH160; + type Erc20MultilocationPrefix = Erc20XcmBridgePalletLocation; + type Erc20TransferGasLimit = Erc20XcmBridgeTransferGasLimit; + type EvmRunner = EvmRunnerPrecompileOrEthXcm; +} + #[cfg(feature = "runtime-benchmarks")] mod testing { use super::*; diff --git a/runtime/moonbeam/tests/integration_test.rs b/runtime/moonbeam/tests/integration_test.rs index 801b4d66b7..e88d5a17b7 100644 --- a/runtime/moonbeam/tests/integration_test.rs +++ b/runtime/moonbeam/tests/integration_test.rs @@ -32,17 +32,17 @@ use frame_support::{ weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, StorageHasher, Twox128, }; - use moonbeam_runtime::{ asset_config::LocalAssetInstance, currency::GLMR, - xcm_config::{CurrencyId, SelfReserve, UnitWeightCost}, + xcm_config::{CurrencyId, SelfReserve}, AccountId, Balances, CouncilCollective, CrowdloanRewards, OpenTechCommitteeCollective, ParachainStaking, PolkadotXcm, Precompiles, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, System, TechCommitteeCollective, TransactionPayment, TreasuryCouncilCollective, XTokens, XcmTransactor, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, LOCAL_ASSET_PRECOMPILE_ADDRESS_PREFIX, }; +use moonbeam_xcm_benchmarks::weights::XcmWeight; use nimbus_primitives::NimbusId; use pallet_evm::PrecompileSet; use pallet_evm_precompileset_assets_erc20::{ @@ -2439,7 +2439,7 @@ fn xtokens_precompile_transfer() { weight: 4_000_000, }, ) - .expect_cost(24000) + .expect_cost(57639) .expect_no_logs() .execute_returns(()) }) @@ -2491,7 +2491,7 @@ fn xtokens_precompile_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(24000) + .expect_cost(57639) .expect_no_logs() .execute_returns(()); }) @@ -2824,7 +2824,7 @@ fn call_xtokens_with_fee() { } #[test] -fn test_xcm_utils_ml_to_account() { +fn test_xcm_utils_ml_tp_account() { ExtBuilder::default().build().execute_with(|| { let xcm_utils_precompile_address = H160::from_low_u64_be(2060); let expected_address_parent: H160 = @@ -2874,8 +2874,13 @@ fn test_xcm_utils_ml_to_account() { }, ), ); + let expected_address_alice_in_parachain_2000: H160 = + xcm_builder::ForeignChainAliasAccount::::convert_ref( + alice_in_parachain_2000_multilocation.clone(), + ) + .unwrap() + .into(); - // this should fail, this convertor is not allowed in moonriver Precompiles::new() .prepare_test( ALICE, @@ -2886,7 +2891,7 @@ fn test_xcm_utils_ml_to_account() { ) .expect_cost(1000) .expect_no_logs() - .execute_reverts(|output| output == b"multilocation: Failed multilocation conversion"); + .execute_returns(Address(expected_address_alice_in_parachain_2000)); }); } @@ -2894,7 +2899,8 @@ fn test_xcm_utils_ml_to_account() { fn test_xcm_utils_weight_message() { ExtBuilder::default().build().execute_with(|| { let xcm_utils_precompile_address = H160::from_low_u64_be(2060); - let expected_weight = UnitWeightCost::get().ref_time(); + let expected_weight = + XcmWeight::::clear_origin().ref_time(); let message: Vec = xcm::VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); diff --git a/runtime/moonbeam/tests/xcm_mock/mod.rs b/runtime/moonbeam/tests/xcm_mock/mod.rs index 50f76f0dbb..46167dbc80 100644 --- a/runtime/moonbeam/tests/xcm_mock/mod.rs +++ b/runtime/moonbeam/tests/xcm_mock/mod.rs @@ -43,6 +43,10 @@ pub fn para_b_account() -> AccountId32 { ParaId::from(2).into_account_truncating() } +pub fn para_a_account_20() -> parachain::AccountId { + ParaId::from(1).into_account_truncating() +} + pub fn evm_account() -> H160 { H160::from_str("1000000000000000000000000000000000000001").unwrap() } diff --git a/runtime/moonbeam/tests/xcm_mock/parachain.rs b/runtime/moonbeam/tests/xcm_mock/parachain.rs index 12e6fbab9d..edaea851b5 100644 --- a/runtime/moonbeam/tests/xcm_mock/parachain.rs +++ b/runtime/moonbeam/tests/xcm_mock/parachain.rs @@ -17,10 +17,13 @@ //! Parachain runtime mock. use frame_support::{ + codec::MaxEncodedLen, construct_runtime, dispatch::GetDispatchInfo, - parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Get, Nothing, PalletInfoAccess}, + ensure, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstU32, Everything, Get, InstanceFilter, Nothing, PalletInfoAccess, + }, weights::Weight, PalletId, }; @@ -31,7 +34,7 @@ use orml_traits::parameter_type_with_key; use parity_scale_codec::{Decode, Encode}; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, Hash, IdentityLookup}, + traits::{BlakeTwo256, Hash, IdentityLookup, Zero}, Permill, }; use sp_std::{convert::TryFrom, prelude::*}; @@ -90,7 +93,7 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = Nothing; + type BaseCallFilter = Everything; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); @@ -178,6 +181,8 @@ pub type LocationToAccountId = ( // Sibling parachain origins convert to AccountId via the `ParaId::into`. SiblingParachainConvertsVia, AccountKey20Aliases, + // Generate remote accounts according to polkadot standards + xcm_builder::ForeignChainAliasAccount, ); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, @@ -277,6 +282,7 @@ pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = ( TakeWeightCredit, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, @@ -331,6 +337,12 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } +use frame_system::RawOrigin; +use sp_runtime::traits::PostDispatchInfoOf; +use sp_runtime::DispatchErrorWithPostInfo; +use xcm_executor::traits::CallDispatcher; +moonbeam_runtime_common::impl_moonbeam_xcm_call!(); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -356,7 +368,7 @@ impl Config for XcmConfig { type SubscriptionService = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; - type CallDispatcher = RuntimeCall; + type CallDispatcher = MoonbeamCall; type AssetLocker = (); type AssetExchanger = (); type PalletInstancesInfo = (); @@ -1013,6 +1025,83 @@ impl pallet_ethereum::Config for Runtime { type PostLogContent = PostBlockAndTxnHashes; } +parameter_types! { + pub ReservedXcmpWeight: Weight = Weight::from_parts(u64::max_value(), 0); +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo, +)] +pub enum ProxyType { + NotAllowed = 0, + Any = 1, +} + +impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {} + +impl InstanceFilter for ProxyType { + fn filter(&self, _c: &RuntimeCall) -> bool { + match self { + ProxyType::NotAllowed => false, + ProxyType::Any => true, + } + } + fn is_superset(&self, _o: &Self) -> bool { + false + } +} + +impl Default for ProxyType { + fn default() -> Self { + Self::NotAllowed + } +} + +parameter_types! { + pub const ProxyCost: u64 = 1; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyCost; + type ProxyDepositFactor = ProxyCost; + type MaxProxies = ConstU32<32>; + type WeightInfo = pallet_proxy::weights::SubstrateWeight; + type MaxPending = ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = ProxyCost; + type AnnouncementDepositFactor = ProxyCost; +} + +pub struct EthereumXcmEnsureProxy; +impl xcm_primitives::EnsureProxy for EthereumXcmEnsureProxy { + fn ensure_ok(delegator: AccountId, delegatee: AccountId) -> Result<(), &'static str> { + // The EVM implicitely contains an Any proxy, so we only allow for "Any" proxies + let def: pallet_proxy::ProxyDefinition = + pallet_proxy::Pallet::::find_proxy( + &delegator, + &delegatee, + Some(ProxyType::Any), + ) + .map_err(|_| "proxy error: expected `ProxyType::Any`")?; + // We only allow to use it for delay zero proxies, as the call will iMmediatly be executed + ensure!(def.delay.is_zero(), "proxy delay is Non-zero`"); + Ok(()) + } +} + +impl pallet_ethereum_xcm::Config for Runtime { + type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; + type XcmEthereumOrigin = pallet_ethereum_xcm::EnsureXcmEthereumTransaction; + type ReservedXcmpWeight = ReservedXcmpWeight; + type EnsureProxy = EthereumXcmEnsureProxy; + type ControllerOrigin = EnsureRoot; +} + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -1035,10 +1124,12 @@ construct_runtime!( XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Storage, Event}, Treasury: pallet_treasury::{Pallet, Storage, Config, Event, Call}, LocalAssets: pallet_assets::::{Pallet, Call, Storage, Event}, + Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage}, EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Origin, Config}, + EthereumXcm: pallet_ethereum_xcm::{Pallet, Call, Origin}, } ); diff --git a/runtime/moonbeam/tests/xcm_mock/relay_chain.rs b/runtime/moonbeam/tests/xcm_mock/relay_chain.rs index 955d5f97e3..6f83328364 100644 --- a/runtime/moonbeam/tests/xcm_mock/relay_chain.rs +++ b/runtime/moonbeam/tests/xcm_mock/relay_chain.rs @@ -139,67 +139,10 @@ parameter_types! { pub MatcherLocation: MultiLocation = MultiLocation::here(); } -use frame_support::ensure; -use frame_support::traits::{Contains, ProcessMessageError}; -use sp_std::marker::PhantomData; -use xcm_executor::traits::ShouldExecute; -/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking -/// payments into account. -/// -/// Only allows for `DescendOrigin` + `WithdrawAsset`, + `BuyExecution` -pub struct AllowTopLevelPaidExecutionDescendOriginFirst(PhantomData); -impl> ShouldExecute for AllowTopLevelPaidExecutionDescendOriginFirst { - fn should_execute( - origin: &MultiLocation, - message: &mut [Instruction], - max_weight: Weight, - _weight_credit: &mut Weight, - ) -> Result<(), ProcessMessageError> { - log::trace!( - target: "xcm::barriers", - "AllowTopLevelPaidExecutionFromLocal origin: - {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, max_weight, _weight_credit, - ); - ensure!(T::contains(origin), ProcessMessageError::Unsupported); - let mut iter = message.iter_mut(); - let mut i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - DescendOrigin(..) => (), - _ => return Err(ProcessMessageError::BadFormat), - } - - i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - WithdrawAsset(..) => (), - _ => return Err(ProcessMessageError::BadFormat), - } - - i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - BuyExecution { - weight_limit: Limited(ref mut weight), - .. - } if weight.all_gte(max_weight) => { - *weight = max_weight; - Ok(()) - } - BuyExecution { - ref mut weight_limit, - .. - } if weight_limit == &Unlimited => { - *weight_limit = Limited(max_weight); - Ok(()) - } - _ => Err(ProcessMessageError::Overweight(max_weight)), - } - } -} - pub type XcmRouter = super::RelayChainXcmRouter; pub type Barrier = ( TakeWeightCredit, - AllowTopLevelPaidExecutionDescendOriginFirst, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, diff --git a/runtime/moonbeam/tests/xcm_tests.rs b/runtime/moonbeam/tests/xcm_tests.rs index 4ed134d57f..2d7a341de4 100644 --- a/runtime/moonbeam/tests/xcm_tests.rs +++ b/runtime/moonbeam/tests/xcm_tests.rs @@ -17,23 +17,25 @@ //! Moonbeam Runtime Xcm Tests mod xcm_mock; + +use cumulus_primitives_core::relay_chain::HrmpChannelId; use frame_support::{ assert_ok, traits::{PalletInfo, PalletInfoAccess}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + BoundedVec, }; -use xcm::{VersionedMultiLocation, WrapVersion}; -use xcm_mock::parachain; -use xcm_mock::relay_chain; -use xcm_mock::*; - -use cumulus_primitives_core::relay_chain::HrmpChannelId; use pallet_asset_manager::LocalAssetIdCreator; use pallet_xcm_transactor::{ Currency, CurrencyPayment, HrmpInitParams, HrmpOperation, TransactWeights, }; +use sp_core::ConstU32; use xcm::latest::prelude::*; +use xcm::{VersionedMultiLocation, WrapVersion}; use xcm_executor::traits::Convert; +use xcm_mock::parachain; +use xcm_mock::relay_chain; +use xcm_mock::*; use xcm_primitives::{UtilityEncodeCall, DEFAULT_PROOF_SIZE}; use xcm_simulator::TestExt; @@ -2319,6 +2321,586 @@ fn transact_through_signed_multilocation() { }); } +#[test] +fn transact_through_signed_multilocation_custom_fee_and_weight() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + ParaA::execute_with(|| { + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the relay will see instead of us + descend_origin_multilocation + .reanchor(&MultiLocation::parent(), ancestry.interior) + .unwrap(); + + let derived = xcm_builder::Account32Hash::< + relay_chain::KusamaNetwork, + relay_chain::AccountId, + >::convert_ref(descend_origin_multilocation) + .unwrap(); + + Relay::execute_with(|| { + // free execution, full amount received + assert_ok!(RelayBalances::transfer( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + derived.clone(), + 4000004100u128, + )); + // derived account has all funds + assert!(RelayBalances::free_balance(&derived) == 4000004100); + // sovereign account has 0 funds + assert!(RelayBalances::free_balance(¶_a_account()) == 0); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = ::PalletInfo::index::< + relay_chain::Balances, + >() + .unwrap() as u8; + + encoded.push(index); + + // Then call bytes + let mut call_bytes = pallet_balances::Call::::transfer { + // 100 to sovereign + dest: para_a_account(), + value: 100u32.into(), + } + .encode(); + encoded.append(&mut call_bytes); + + let total_weight = 4000004000u64; + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(MultiLocation::parent())), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + MultiLocation::parent() + ))), + fee_amount: Some(total_weight as u128) + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: Some(total_weight.into()) + } + )); + }); + + Relay::execute_with(|| { + assert!(RelayBalances::free_balance(¶_a_account()) == 100); + + assert!(RelayBalances::free_balance(&derived) == 0); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + ParaB::execute_with(|| { + // free execution, full amount received + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + // Then call bytes + let mut call_bytes = pallet_balances::Call::::transfer { + // 100 to sovereign + dest: para_a_account_20(), + value: 100u32.into(), + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + // 1-1 to fee + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + assert!(ParaBalances::free_balance(&derived) == 0); + + assert!(ParaBalances::free_balance(¶_a_account_20()) == 100); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let mut parachain_b_alice_balances_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + parachain_b_alice_balances_before = ParaBalances::free_balance(&PARAALICE.into()) + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(21000), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(PARAALICE.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact { + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + // 1-1 to fee + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer went through + assert!( + ParaBalances::free_balance(&PARAALICE.into()) + == parachain_b_alice_balances_before + 100 + ); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum_no_proxy_fails() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let mut parachain_b_alice_balances_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + parachain_b_alice_balances_before = ParaBalances::free_balance(&PARAALICE.into()) + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(21000), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(PARAALICE.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact_through_proxy { + transact_as: PARAALICE.into(), + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer wasn't executed + assert!(ParaBalances::free_balance(&PARAALICE.into()) == parachain_b_alice_balances_before); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum_proxy_succeeds() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let transfer_recipient = evm_account(); + let mut transfer_recipient_balance_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + transfer_recipient_balance_before = ParaBalances::free_balance(&transfer_recipient.into()); + + // Add proxy ALICE -> derived + let _ = parachain::Proxy::add_proxy_delegate( + &PARAALICE.into(), + derived, + parachain::ProxyType::Any, + 0, + ); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V2(xcm_primitives::EthereumXcmTransactionV2 { + gas_limit: U256::from(21000), + action: pallet_ethereum::TransactionAction::Call(transfer_recipient.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact_through_proxy { + transact_as: PARAALICE.into(), + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer was executed + assert!( + ParaBalances::free_balance(&transfer_recipient.into()) + == transfer_recipient_balance_before + 100 + ); + }); +} + #[test] fn hrmp_init_accept_through_root() { MockNet::reset(); diff --git a/runtime/moonriver/Cargo.toml b/runtime/moonriver/Cargo.toml index 68a7e22354..8b5c608a53 100644 --- a/runtime/moonriver/Cargo.toml +++ b/runtime/moonriver/Cargo.toml @@ -33,7 +33,9 @@ moonbeam-xcm-benchmarks = { workspace = true } pallet-asset-manager = { workspace = true } pallet-author-mapping = { workspace = true } pallet-crowdloan-rewards = { workspace = true } +pallet-erc20-xcm-bridge = { workspace = true } pallet-ethereum-chain-id = { workspace = true } +pallet-ethereum-xcm = { workspace = true } pallet-maintenance-mode = { workspace = true, features = [ "xcm-support" ] } pallet-migrations = { workspace = true } pallet-moonbeam-orbiters = { workspace = true } @@ -206,7 +208,9 @@ std = [ "pallet-conviction-voting/std", "pallet-crowdloan-rewards/std", "pallet-democracy/std", + "pallet-erc20-xcm-bridge/std", "pallet-ethereum-chain-id/std", + "pallet-ethereum-xcm/std", "pallet-ethereum/std", "pallet-evm-precompile-author-mapping/std", "pallet-evm-precompile-balances-erc20/std", @@ -301,6 +305,7 @@ runtime-benchmarks = [ "pallet-conviction-voting/runtime-benchmarks", "pallet-crowdloan-rewards/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", + "pallet-ethereum-xcm/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-moonbeam-orbiters/runtime-benchmarks", "pallet-parachain-staking/runtime-benchmarks", diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index e93b5bb567..fe08d9b083 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -40,6 +40,7 @@ pub use frame_support::traits::Get; use frame_support::{ construct_runtime, dispatch::{DispatchClass, GetDispatchInfo}, + ensure, pallet_prelude::DispatchResult, parameter_types, traits::{ @@ -77,7 +78,7 @@ use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, IdentityLookup, - PostDispatchInfoOf, UniqueSaturatedInto, + PostDispatchInfoOf, UniqueSaturatedInto, Zero, }, transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, @@ -609,6 +610,32 @@ impl pallet_ethereum::Config for Runtime { type PostLogContent = PostBlockAndTxnHashes; } +pub struct EthereumXcmEnsureProxy; +impl xcm_primitives::EnsureProxy for EthereumXcmEnsureProxy { + fn ensure_ok(delegator: AccountId, delegatee: AccountId) -> Result<(), &'static str> { + // The EVM implicitely contains an Any proxy, so we only allow for "Any" proxies + let def: pallet_proxy::ProxyDefinition = + pallet_proxy::Pallet::::find_proxy( + &delegator, + &delegatee, + Some(ProxyType::Any), + ) + .map_err(|_| "proxy error: expected `ProxyType::Any`")?; + // We only allow to use it for delay zero proxies, as the call will immediatly be executed + ensure!(def.delay.is_zero(), "proxy delay is Non-zero`"); + Ok(()) + } +} + +impl pallet_ethereum_xcm::Config for Runtime { + type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; + type XcmEthereumOrigin = pallet_ethereum_xcm::EnsureXcmEthereumTransaction; + type ReservedXcmpWeight = ReservedXcmpWeight; + type EnsureProxy = EthereumXcmEnsureProxy; + type ControllerOrigin = EnsureRoot; +} + parameter_types! { pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); @@ -1008,6 +1035,7 @@ impl Contains for MaintenanceFilter { RuntimeCall::PolkadotXcm(_) => false, RuntimeCall::Treasury(_) => false, RuntimeCall::XcmTransactor(_) => false, + RuntimeCall::EthereumXcm(_) => false, _ => true, } } @@ -1351,6 +1379,8 @@ construct_runtime! { XTokens: orml_xtokens::{Pallet, Call, Storage, Event} = 106, XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Storage, Event} = 107, LocalAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 108, + EthereumXcm: pallet_ethereum_xcm::{Pallet, Call, Storage, Origin} = 109, + Erc20XcmBridge: pallet_erc20_xcm_bridge::{Pallet} = 110, // Randomness Randomness: pallet_randomness::{Pallet, Call, Storage, Event, Inherent} = 120, diff --git a/runtime/moonriver/src/xcm_config.rs b/runtime/moonriver/src/xcm_config.rs index beddf30ca2..805d36076c 100644 --- a/runtime/moonriver/src/xcm_config.rs +++ b/runtime/moonriver/src/xcm_config.rs @@ -19,12 +19,15 @@ use super::{ governance, AccountId, AssetId, AssetManager, Assets, Balance, Balances, DealWithFees, - LocalAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, - RuntimeOrigin, Treasury, XcmpQueue, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, + Erc20XcmBridge, LocalAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, }; use pallet_evm_precompileset_assets_erc20::AccountIdAssetIdConversion; -use sp_runtime::traits::Hash as THash; +use sp_runtime::{ + traits::{Hash as THash, PostDispatchInfoOf}, + DispatchErrorWithPostInfo, +}; use frame_support::{ dispatch::Weight, @@ -32,20 +35,20 @@ use frame_support::{ traits::{EitherOfDiverse, Everything, Nothing, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureRoot, RawOrigin}; use sp_core::{ConstU32, H160, H256}; use xcm_builder::{ AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, - NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation, - TakeWeightCredit, UsingComponents, + CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FungiblesAdapter, NoChecking, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountKey20AsNative, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + WeightInfoBounds, }; use xcm::latest::prelude::*; -use xcm_executor::traits::JustTry; +use xcm_executor::traits::{CallDispatcher, JustTry}; use orml_xcm_support::MultiNativeAsset; use xcm_primitives::{ @@ -106,8 +109,21 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // If we receive a MultiLocation of type AccountKey20, just generate a native account AccountKey20Aliases, + // Generate remote accounts according to polkadot standards + xcm_builder::ForeignChainAliasAccount, ); +/// Wrapper type around `LocationToAccountId` to convert an `AccountId` to type `H160`. +pub struct LocationToH160; +impl xcm_executor::traits::Convert for LocationToH160 { + fn convert(location: MultiLocation) -> Result { + >::convert( + location, + ) + .map(Into::into) + } +} + // The non-reserve fungible transactor type // It will use pallet-assets, and the Id will be matched against AsAssetType pub type ForeignFungiblesTransactor = FungiblesAdapter< @@ -215,11 +231,16 @@ parameter_types! { } /// Xcm Weigher shared between multiple Xcm-related configs. -pub type XcmWeigher = FixedWeightBounds; +pub type XcmWeigher = WeightInfoBounds< + moonbeam_xcm_benchmarks::weights::XcmWeight, + RuntimeCall, + MaxInstructions, +>; // Allow paid executions pub type XcmBarrier = ( TakeWeightCredit, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, @@ -262,6 +283,15 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = xcm_primitives::MAX_ASSETS; } +// Our implementation of the Moonbeam Call +// Attachs the right origin in case the call is made to pallet-ethereum-xcm +#[cfg(not(feature = "evm-tracing"))] +moonbeam_runtime_common::impl_moonbeam_xcm_call!(); +#[cfg(feature = "evm-tracing")] +moonbeam_runtime_common::impl_moonbeam_xcm_call_tracing!(); + +moonbeam_runtime_common::impl_evm_runner_precompile_or_eth_xcm!(); + pub struct XcmExecutorConfig; impl xcm_executor::Config for XcmExecutorConfig { type RuntimeCall = RuntimeCall; @@ -294,9 +324,9 @@ impl xcm_executor::Config for XcmExecutorConfig { ); type ResponseHandler = PolkadotXcm; type SubscriptionService = PolkadotXcm; - type AssetTrap = PolkadotXcm; + type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper; type AssetClaims = PolkadotXcm; - type CallDispatcher = RuntimeCall; + type CallDispatcher = MoonbeamCall; type PalletInstancesInfo = crate::AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); @@ -308,7 +338,10 @@ impl xcm_executor::Config for XcmExecutorConfig { type AssetIsBurnable = Everything; } -type XcmExecutor = xcm_executor::XcmExecutor; +type XcmExecutor = pallet_erc20_xcm_bridge::XcmExecutorWrapper< + RuntimeCall, + xcm_executor::XcmExecutor, +>; // Converts a Signed Local Origin into a MultiLocation pub type LocalOriginToLocation = SignedToAccountId20; @@ -587,6 +620,29 @@ impl pallet_xcm_transactor::Config for Runtime { type HrmpEncoder = moonbeam_relay_encoder::kusama::KusamaEncoder; } +parameter_types! { + // This is the relative view of erc20 assets. + // Identified by this prefix + AccountKey20(contractAddress) + // We use the RELATIVE multilocation + pub Erc20XcmBridgePalletLocation: MultiLocation = MultiLocation { + parents:0, + interior: Junctions::X1( + PalletInstance(::index() as u8) + ) + }; + + // To be able to support almost all erc20 implementations, + // we provide a sufficiently hight gas limit. + pub Erc20XcmBridgeTransferGasLimit: u64 = 80_000; +} + +impl pallet_erc20_xcm_bridge::Config for Runtime { + type AccountIdConverter = LocationToH160; + type Erc20MultilocationPrefix = Erc20XcmBridgePalletLocation; + type Erc20TransferGasLimit = Erc20XcmBridgeTransferGasLimit; + type EvmRunner = EvmRunnerPrecompileOrEthXcm; +} + #[cfg(feature = "runtime-benchmarks")] mod testing { use super::*; diff --git a/runtime/moonriver/tests/integration_test.rs b/runtime/moonriver/tests/integration_test.rs index 3e926e4e7c..e01bfd78a2 100644 --- a/runtime/moonriver/tests/integration_test.rs +++ b/runtime/moonriver/tests/integration_test.rs @@ -32,9 +32,10 @@ use frame_support::{ weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, StorageHasher, Twox128, }; +use moonbeam_xcm_benchmarks::weights::XcmWeight; use moonriver_runtime::{ asset_config::LocalAssetInstance, - xcm_config::{CurrencyId, SelfReserve, UnitWeightCost}, + xcm_config::{CurrencyId, SelfReserve}, AssetId, CouncilCollective, LocalAssets, OpenTechCommitteeCollective, PolkadotXcm, Precompiles, RuntimeBlockWeights, TechCommitteeCollective, TransactionPayment, TreasuryCouncilCollective, XTokens, XcmTransactor, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, @@ -2413,7 +2414,7 @@ fn xtokens_precompiles_transfer() { weight: 4_000_000, }, ) - .expect_cost(24000) + .expect_cost(57639) .expect_no_logs() .execute_returns(()) }) @@ -2465,7 +2466,7 @@ fn xtokens_precompiles_transfer_multiasset() { weight: 4_000_000, }, ) - .expect_cost(24000) + .expect_cost(57639) .expect_no_logs() .execute_returns(()); }) @@ -2732,7 +2733,7 @@ fn call_xtokens_with_fee() { } #[test] -fn test_xcm_utils_ml_to_account() { +fn test_xcm_utils_ml_tp_account() { ExtBuilder::default().build().execute_with(|| { let xcm_utils_precompile_address = H160::from_low_u64_be(2060); let expected_address_parent: H160 = @@ -2782,8 +2783,13 @@ fn test_xcm_utils_ml_to_account() { }, ), ); + let expected_address_alice_in_parachain_2000: H160 = + xcm_builder::ForeignChainAliasAccount::::convert_ref( + alice_in_parachain_2000_multilocation.clone(), + ) + .unwrap() + .into(); - // this should fail, this convertor is not allowed in moonriver Precompiles::new() .prepare_test( ALICE, @@ -2794,7 +2800,7 @@ fn test_xcm_utils_ml_to_account() { ) .expect_cost(1000) .expect_no_logs() - .execute_reverts(|output| output == b"multilocation: Failed multilocation conversion"); + .execute_returns(Address(expected_address_alice_in_parachain_2000)); }); } @@ -2802,7 +2808,8 @@ fn test_xcm_utils_ml_to_account() { fn test_xcm_utils_weight_message() { ExtBuilder::default().build().execute_with(|| { let xcm_utils_precompile_address = H160::from_low_u64_be(2060); - let expected_weight = UnitWeightCost::get().ref_time(); + let expected_weight = + XcmWeight::::clear_origin().ref_time(); let message: Vec = xcm::VersionedXcm::<()>::V3(Xcm(vec![ClearOrigin])).encode(); diff --git a/runtime/moonriver/tests/xcm_mock/mod.rs b/runtime/moonriver/tests/xcm_mock/mod.rs index 673cd7dd2c..c1275888af 100644 --- a/runtime/moonriver/tests/xcm_mock/mod.rs +++ b/runtime/moonriver/tests/xcm_mock/mod.rs @@ -43,6 +43,10 @@ pub fn para_b_account() -> AccountId32 { ParaId::from(2).into_account_truncating() } +pub fn para_a_account_20() -> parachain::AccountId { + ParaId::from(1).into_account_truncating() +} + pub fn evm_account() -> H160 { H160::from_str("1000000000000000000000000000000000000001").unwrap() } diff --git a/runtime/moonriver/tests/xcm_mock/parachain.rs b/runtime/moonriver/tests/xcm_mock/parachain.rs index d69bd6b890..9b3480a448 100644 --- a/runtime/moonriver/tests/xcm_mock/parachain.rs +++ b/runtime/moonriver/tests/xcm_mock/parachain.rs @@ -17,10 +17,13 @@ //! Parachain runtime mock. use frame_support::{ + codec::MaxEncodedLen, construct_runtime, dispatch::GetDispatchInfo, - parameter_types, - traits::{AsEnsureOriginWithArg, Everything, Get, Nothing, PalletInfoAccess}, + ensure, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstU32, Everything, Get, InstanceFilter, Nothing, PalletInfoAccess, + }, weights::Weight, PalletId, }; @@ -28,7 +31,7 @@ use frame_system::{EnsureNever, EnsureRoot}; use parity_scale_codec::{Decode, Encode}; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, Hash, IdentityLookup}, + traits::{BlakeTwo256, Hash, IdentityLookup, Zero}, Permill, }; use sp_std::{convert::TryFrom, prelude::*}; @@ -89,7 +92,7 @@ impl frame_system::Config for Runtime { type OnNewAccount = (); type OnKilledAccount = (); type DbWeight = (); - type BaseCallFilter = Nothing; + type BaseCallFilter = Everything; type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); @@ -177,6 +180,8 @@ pub type LocationToAccountId = ( // Sibling parachain origins convert to AccountId via the `ParaId::into`. SiblingParachainConvertsVia, AccountKey20Aliases, + // Generate remote accounts according to polkadot standards + xcm_builder::ForeignChainAliasAccount, ); /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, @@ -277,6 +282,7 @@ pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = ( TakeWeightCredit, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, @@ -338,6 +344,12 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } +use frame_system::RawOrigin; +use sp_runtime::traits::PostDispatchInfoOf; +use sp_runtime::DispatchErrorWithPostInfo; +use xcm_executor::traits::CallDispatcher; +moonbeam_runtime_common::impl_moonbeam_xcm_call!(); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -364,7 +376,7 @@ impl Config for XcmConfig { type SubscriptionService = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; - type CallDispatcher = RuntimeCall; + type CallDispatcher = MoonbeamCall; type AssetLocker = (); type AssetExchanger = (); type PalletInstancesInfo = (); @@ -1023,6 +1035,83 @@ impl pallet_ethereum::Config for Runtime { type PostLogContent = PostBlockAndTxnHashes; } +parameter_types! { + pub ReservedXcmpWeight: Weight = Weight::from_parts(u64::max_value(), 0); +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo, +)] +pub enum ProxyType { + NotAllowed = 0, + Any = 1, +} + +impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {} + +impl InstanceFilter for ProxyType { + fn filter(&self, _c: &RuntimeCall) -> bool { + match self { + ProxyType::NotAllowed => false, + ProxyType::Any => true, + } + } + fn is_superset(&self, _o: &Self) -> bool { + false + } +} + +impl Default for ProxyType { + fn default() -> Self { + Self::NotAllowed + } +} + +parameter_types! { + pub const ProxyCost: u64 = 1; +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyCost; + type ProxyDepositFactor = ProxyCost; + type MaxProxies = ConstU32<32>; + type WeightInfo = pallet_proxy::weights::SubstrateWeight; + type MaxPending = ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = ProxyCost; + type AnnouncementDepositFactor = ProxyCost; +} + +pub struct EthereumXcmEnsureProxy; +impl xcm_primitives::EnsureProxy for EthereumXcmEnsureProxy { + fn ensure_ok(delegator: AccountId, delegatee: AccountId) -> Result<(), &'static str> { + // The EVM implicitely contains an Any proxy, so we only allow for "Any" proxies + let def: pallet_proxy::ProxyDefinition = + pallet_proxy::Pallet::::find_proxy( + &delegator, + &delegatee, + Some(ProxyType::Any), + ) + .map_err(|_| "proxy error: expected `ProxyType::Any`")?; + // We only allow to use it for delay zero proxies, as the call will iMmediatly be executed + ensure!(def.delay.is_zero(), "proxy delay is Non-zero`"); + Ok(()) + } +} + +impl pallet_ethereum_xcm::Config for Runtime { + type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper; + type ValidatedTransaction = pallet_ethereum::ValidatedTransaction; + type XcmEthereumOrigin = pallet_ethereum_xcm::EnsureXcmEthereumTransaction; + type ReservedXcmpWeight = ReservedXcmpWeight; + type EnsureProxy = EthereumXcmEnsureProxy; + type ControllerOrigin = EnsureRoot; +} + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -1045,10 +1134,12 @@ construct_runtime!( XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Storage, Event}, Treasury: pallet_treasury::{Pallet, Storage, Config, Event, Call}, LocalAssets: pallet_assets::::{Pallet, Call, Storage, Event}, + Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage}, EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Origin, Config}, + EthereumXcm: pallet_ethereum_xcm::{Pallet, Call, Origin}, } ); diff --git a/runtime/moonriver/tests/xcm_mock/relay_chain.rs b/runtime/moonriver/tests/xcm_mock/relay_chain.rs index 955d5f97e3..6f83328364 100644 --- a/runtime/moonriver/tests/xcm_mock/relay_chain.rs +++ b/runtime/moonriver/tests/xcm_mock/relay_chain.rs @@ -139,67 +139,10 @@ parameter_types! { pub MatcherLocation: MultiLocation = MultiLocation::here(); } -use frame_support::ensure; -use frame_support::traits::{Contains, ProcessMessageError}; -use sp_std::marker::PhantomData; -use xcm_executor::traits::ShouldExecute; -/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking -/// payments into account. -/// -/// Only allows for `DescendOrigin` + `WithdrawAsset`, + `BuyExecution` -pub struct AllowTopLevelPaidExecutionDescendOriginFirst(PhantomData); -impl> ShouldExecute for AllowTopLevelPaidExecutionDescendOriginFirst { - fn should_execute( - origin: &MultiLocation, - message: &mut [Instruction], - max_weight: Weight, - _weight_credit: &mut Weight, - ) -> Result<(), ProcessMessageError> { - log::trace!( - target: "xcm::barriers", - "AllowTopLevelPaidExecutionFromLocal origin: - {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, message, max_weight, _weight_credit, - ); - ensure!(T::contains(origin), ProcessMessageError::Unsupported); - let mut iter = message.iter_mut(); - let mut i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - DescendOrigin(..) => (), - _ => return Err(ProcessMessageError::BadFormat), - } - - i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - WithdrawAsset(..) => (), - _ => return Err(ProcessMessageError::BadFormat), - } - - i = iter.next().ok_or(ProcessMessageError::BadFormat)?; - match i { - BuyExecution { - weight_limit: Limited(ref mut weight), - .. - } if weight.all_gte(max_weight) => { - *weight = max_weight; - Ok(()) - } - BuyExecution { - ref mut weight_limit, - .. - } if weight_limit == &Unlimited => { - *weight_limit = Limited(max_weight); - Ok(()) - } - _ => Err(ProcessMessageError::Overweight(max_weight)), - } - } -} - pub type XcmRouter = super::RelayChainXcmRouter; pub type Barrier = ( TakeWeightCredit, - AllowTopLevelPaidExecutionDescendOriginFirst, + xcm_primitives::AllowTopLevelPaidExecutionDescendOriginFirst, AllowTopLevelPaidExecutionFrom, // Expected responses are OK. AllowKnownQueryResponses, diff --git a/runtime/moonriver/tests/xcm_tests.rs b/runtime/moonriver/tests/xcm_tests.rs index bbe6291f1d..86a0297eb2 100644 --- a/runtime/moonriver/tests/xcm_tests.rs +++ b/runtime/moonriver/tests/xcm_tests.rs @@ -21,8 +21,10 @@ use frame_support::{ assert_ok, traits::{PalletInfo, PalletInfoAccess}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + BoundedVec, }; use pallet_asset_manager::LocalAssetIdCreator; +use sp_core::ConstU32; use xcm::latest::prelude::*; use xcm::{VersionedMultiLocation, WrapVersion}; use xcm_executor::traits::Convert; @@ -2631,6 +2633,586 @@ fn transact_through_signed_multilocation() { }); } +#[test] +fn transact_through_signed_multilocation_custom_fee_and_weight() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + ParaA::execute_with(|| { + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the relay will see instead of us + descend_origin_multilocation + .reanchor(&MultiLocation::parent(), ancestry.interior) + .unwrap(); + + let derived = xcm_builder::Account32Hash::< + relay_chain::KusamaNetwork, + relay_chain::AccountId, + >::convert_ref(descend_origin_multilocation) + .unwrap(); + + Relay::execute_with(|| { + // free execution, full amount received + assert_ok!(RelayBalances::transfer( + relay_chain::RuntimeOrigin::signed(RELAYALICE), + derived.clone(), + 4000004100u128, + )); + // derived account has all funds + assert!(RelayBalances::free_balance(&derived) == 4000004100); + // sovereign account has 0 funds + assert!(RelayBalances::free_balance(¶_a_account()) == 0); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = ::PalletInfo::index::< + relay_chain::Balances, + >() + .unwrap() as u8; + + encoded.push(index); + + // Then call bytes + let mut call_bytes = pallet_balances::Call::::transfer { + // 100 to sovereign + dest: para_a_account(), + value: 100u32.into(), + } + .encode(); + encoded.append(&mut call_bytes); + + let total_weight = 4000004000u64; + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(MultiLocation::parent())), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + MultiLocation::parent() + ))), + fee_amount: Some(total_weight as u128) + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: Some(total_weight.into()) + } + )); + }); + + Relay::execute_with(|| { + assert!(RelayBalances::free_balance(¶_a_account()) == 100); + + assert!(RelayBalances::free_balance(&derived) == 0); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + ParaB::execute_with(|| { + // free execution, full amount received + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + // Then call bytes + let mut call_bytes = pallet_balances::Call::::transfer { + // 100 to sovereign + dest: para_a_account_20(), + value: 100u32.into(), + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + // 1-1 to fee + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + assert!(ParaBalances::free_balance(&derived) == 0); + + assert!(ParaBalances::free_balance(¶_a_account_20()) == 100); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let mut parachain_b_alice_balances_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + parachain_b_alice_balances_before = ParaBalances::free_balance(&PARAALICE.into()) + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(21000), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(PARAALICE.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact { + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + // 4000000000 for transfer + 4000 for XCM + // 1-1 to fee + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer went through + assert!( + ParaBalances::free_balance(&PARAALICE.into()) + == parachain_b_alice_balances_before + 100 + ); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum_no_proxy_fails() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let mut parachain_b_alice_balances_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + parachain_b_alice_balances_before = ParaBalances::free_balance(&PARAALICE.into()) + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V1(xcm_primitives::EthereumXcmTransactionV1 { + gas_limit: U256::from(21000), + fee_payment: xcm_primitives::EthereumXcmFee::Auto, + action: pallet_ethereum::TransactionAction::Call(PARAALICE.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact_through_proxy { + transact_as: PARAALICE.into(), + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer wasn't executed + assert!(ParaBalances::free_balance(&PARAALICE.into()) == parachain_b_alice_balances_before); + }); +} + +#[test] +fn transact_through_signed_multilocation_para_to_para_ethereum_proxy_succeeds() { + MockNet::reset(); + let mut ancestry = MultiLocation::parent(); + + let para_b_location = MultiLocation::new(1, X1(Parachain(2))); + + let para_b_balances = MultiLocation::new(1, X2(Parachain(2), PalletInstance(1u8))); + + ParaA::execute_with(|| { + // Root can set transact info + assert_ok!(XcmTransactor::set_transact_info( + parachain::RuntimeOrigin::root(), + // ParaB + Box::new(xcm::VersionedMultiLocation::V3(para_b_location.clone())), + // Para charges 1000 for every instruction, and we have 3, so 3 + 3.into(), + 20000000000.into(), + // 4 instructions in transact through signed + Some(4.into()) + )); + // Root can set transact info + assert_ok!(XcmTransactor::set_fee_per_second( + parachain::RuntimeOrigin::root(), + Box::new(xcm::VersionedMultiLocation::V3(para_b_balances.clone())), + parachain::ParaTokensPerSecond::get().1 as u128, + )); + ancestry = parachain::UniversalLocation::get().into(); + }); + + // Let's construct the Junction that we will append with DescendOrigin + let signed_origin: Junctions = X1(AccountKey20 { + network: None, + key: PARAALICE, + }); + + let mut descend_origin_multilocation = parachain::SelfLocation::get(); + descend_origin_multilocation + .append_with(signed_origin) + .unwrap(); + + // To convert it to what the paraB will see instead of us + descend_origin_multilocation + .reanchor(¶_b_location, ancestry.interior) + .unwrap(); + + let derived = xcm_builder::ForeignChainAliasAccount::::convert_ref( + descend_origin_multilocation, + ) + .unwrap(); + + let transfer_recipient = evm_account(); + let mut transfer_recipient_balance_before = 0; + ParaB::execute_with(|| { + assert_ok!(ParaBalances::transfer( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + derived.clone(), + 4000000104u128, + )); + // derived account has all funds + assert!(ParaBalances::free_balance(&derived) == 4000000104); + // sovereign account has 0 funds + assert!(ParaBalances::free_balance(¶_a_account_20()) == 0); + + transfer_recipient_balance_before = ParaBalances::free_balance(&transfer_recipient.into()); + + // Add proxy ALICE -> derived + let _ = parachain::Proxy::add_proxy_delegate( + &PARAALICE.into(), + derived, + parachain::ProxyType::Any, + 0, + ); + }); + + // Encode the call. Balances transact to para_a_account + // First index + let mut encoded: Vec = Vec::new(); + let index = + ::PalletInfo::index::() + .unwrap() as u8; + + encoded.push(index); + + use sp_core::U256; + // Let's do a EVM transfer + let eth_tx = + xcm_primitives::EthereumXcmTransaction::V2(xcm_primitives::EthereumXcmTransactionV2 { + gas_limit: U256::from(21000), + action: pallet_ethereum::TransactionAction::Call(transfer_recipient.into()), + value: U256::from(100), + input: BoundedVec::< + u8, + ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }> + >::try_from(vec![]).unwrap(), + access_list: None, + }); + + // Then call bytes + let mut call_bytes = pallet_ethereum_xcm::Call::::transact_through_proxy { + transact_as: PARAALICE.into(), + xcm_transaction: eth_tx, + } + .encode(); + encoded.append(&mut call_bytes); + + ParaA::execute_with(|| { + assert_ok!(XcmTransactor::transact_through_signed( + parachain::RuntimeOrigin::signed(PARAALICE.into()), + Box::new(xcm::VersionedMultiLocation::V3(para_b_location)), + CurrencyPayment { + currency: Currency::AsMultiLocation(Box::new(xcm::VersionedMultiLocation::V3( + para_b_balances + ))), + fee_amount: None + }, + encoded, + TransactWeights { + transact_required_weight_at_most: 4000000000.into(), + overall_weight: None + } + )); + }); + + ParaB::execute_with(|| { + // Make sure the EVM transfer was executed + assert!( + ParaBalances::free_balance(&transfer_recipient.into()) + == transfer_recipient_balance_before + 100 + ); + }); +} + #[test] fn hrmp_init_accept_through_root() { MockNet::reset(); diff --git a/tests/util/setup-dev-tests.ts b/tests/util/setup-dev-tests.ts index 5146d6cd6e..36a6a49178 100644 --- a/tests/util/setup-dev-tests.ts +++ b/tests/util/setup-dev-tests.ts @@ -324,3 +324,14 @@ export function describeDevMoonbeamAllEthTxTypes( describeDevMoonbeam(title + " (EIP1559)", cb, "EIP1559", "moonbase", wasm); describeDevMoonbeam(title + " (EIP2930)", cb, "EIP2930", "moonbase", wasm); } + +export function describeDevMoonbeamAllRuntimes( + title: string, + cb: (context: DevTestContext) => void, + withWasm?: boolean +) { + let wasm = withWasm !== undefined ? withWasm : false; + describeDevMoonbeam(title + " (moonbase)", cb, "Legacy", "moonbase", wasm); + describeDevMoonbeam(title + " (moonriver)", cb, "Legacy", "moonriver", wasm); + describeDevMoonbeam(title + " (moonbeam)", cb, "Legacy", "moonbeam", wasm); +}