moonbeam_runtime/
xcm_config.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! XCM configuration for Moonbase.
18//!
19
20use super::{
21	bridge_config, governance, AccountId, AssetId, Balance, Balances, BridgeXcmOverMoonriver,
22	EmergencyParaXcm, Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue,
23	OpenTechCommitteeInstance, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime,
24	RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmWeightTrader,
25	XcmpQueue,
26};
27
28use super::moonbeam_weights;
29use frame_support::{
30	parameter_types,
31	traits::{EitherOf, EitherOfDiverse, Everything, Nothing, PalletInfoAccess, TransformOrigin},
32};
33use moonkit_xcm_primitives::AccountIdAssetIdConversion;
34use sp_runtime::{
35	traits::{Hash as THash, MaybeEquivalence, PostDispatchInfoOf},
36	DispatchErrorWithPostInfo,
37};
38use sp_weights::Weight;
39
40use frame_system::{EnsureRoot, RawOrigin};
41use sp_core::{ConstU32, H160, H256};
42
43use xcm_builder::{
44	AccountKey20Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
45	AllowTopLevelPaidExecutionFrom, Case, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin,
46	ExternalConsensusLocationsConverterFor, FungibleAdapter as XcmCurrencyAdapter,
47	HashedDescription, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
48	SiblingParachainConvertsVia, SignedAccountKey20AsNative, SovereignSignedViaLocation,
49	TakeWeightCredit, TrailingSetTopicAsId, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
50};
51
52use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling};
53use xcm::{
54	latest::prelude::{
55		AllOf, Asset, AssetFilter, GlobalConsensus, InteriorLocation, Junction, Location,
56		NetworkId, PalletInstance, Parachain, Wild, WildFungible,
57	},
58	IntoVersion,
59};
60
61use xcm_executor::traits::{CallDispatcher, ConvertLocation};
62
63use cumulus_primitives_core::{AggregateMessageOrigin, ParaId};
64use frame_support::traits::Disabled;
65use pallet_xcm::EnsureXcm;
66use xcm_primitives::{
67	AbsoluteAndRelativeReserve, AccountIdToCurrencyId, AccountIdToLocation,
68	IsBridgedConcreteAssetFrom, MultiNativeAsset, SignedToAccountId20, XcmTransact,
69};
70
71use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot};
72use crate::runtime_params::dynamic_params;
73use moonbeam_runtime_common::xcm_origins::AllowSiblingParachains;
74use pallet_moonbeam_foreign_assets::{MapSuccessToGovernance, MapSuccessToXcm};
75use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
76use scale_info::TypeInfo;
77use sp_core::Get;
78use sp_std::{
79	convert::{From, Into, TryFrom},
80	prelude::*,
81};
82
83parameter_types! {
84	// The network Id of the relay
85	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
86	// The relay chain Origin type
87	pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
88	pub UniversalLocation: InteriorLocation =
89		[GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into();
90	// Self Reserve location, defines the multilocation identifiying the self-reserve currency
91	// This is used to match it also against our Balances pallet when we receive such
92	// a Location: (Self Balances pallet index)
93	// We use the RELATIVE multilocation
94	pub SelfReserve: Location = Location {
95		parents:0,
96		interior: [
97			PalletInstance(<Balances as PalletInfoAccess>::index() as u8)
98		].into()
99	};
100}
101
102/// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
103/// when determining ownership of accounts for asset transacting and when attempting to use XCM
104/// `Transact` in order to determine the dispatch Origin.
105pub type LocationToAccountId = (
106	// The parent (Relay-chain) origin converts to the default `AccountId`.
107	ParentIsPreset<AccountId>,
108	// Sibling parachain origins convert to AccountId via the `ParaId::into`.
109	SiblingParachainConvertsVia<polkadot_parachain::primitives::Sibling, AccountId>,
110	// If we receive a Location of type AccountKey20, just generate a native account
111	AccountKey20Aliases<RelayNetwork, AccountId>,
112	// Foreign locations alias into accounts according to a hash of their standard description.
113	HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
114	// Different global consensus parachain sovereign account.
115	// (Used for over-bridge transfers and reserve processing)
116	ExternalConsensusLocationsConverterFor<UniversalLocation, AccountId>,
117);
118
119/// Wrapper type around `LocationToAccountId` to convert an `AccountId` to type `H160`.
120pub struct LocationToH160;
121impl ConvertLocation<H160> for LocationToH160 {
122	fn convert_location(location: &Location) -> Option<H160> {
123		<LocationToAccountId as ConvertLocation<AccountId>>::convert_location(location)
124			.map(Into::into)
125	}
126}
127
128/// The transactor for our own chain currency.
129pub type LocalAssetTransactor = XcmCurrencyAdapter<
130	// Use this currency:
131	Balances,
132	// Use this currency when it is a fungible asset matching any of the locations in
133	// SelfReserveRepresentations
134	xcm_builder::IsConcrete<SelfReserve>,
135	// We can convert the MultiLocations with our converter above:
136	LocationToAccountId,
137	// Our chain's account ID type (we can't get away without mentioning it explicitly):
138	AccountId,
139	// We dont allow teleport
140	(),
141>;
142
143// We use all transactors
144pub type AssetTransactors = (LocalAssetTransactor, EvmForeignAssets, Erc20XcmBridge);
145
146/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
147/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
148/// biases the kind of local `Origin` it will become.
149pub type XcmOriginToTransactDispatchOrigin = (
150	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
151	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
152	// foreign chains who want to have a local sovereign account on this chain which they control.
153	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
154	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
155	// recognised.
156	RelayChainAsNative<RelayChainOrigin, RuntimeOrigin>,
157	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
158	// recognised.
159	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
160	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
161	pallet_xcm::XcmPassthrough<RuntimeOrigin>,
162	// Xcm Origins defined by a Multilocation of type AccountKey20 can be converted to a 20 byte-
163	// account local origin
164	SignedAccountKey20AsNative<RelayNetwork, RuntimeOrigin>,
165);
166
167parameter_types! {
168	/// Maximum number of instructions in a single XCM fragment. A sanity check against
169	/// weight caculations getting too crazy.
170	pub MaxInstructions: u32 = 100;
171}
172
173/// Xcm Weigher shared between multiple Xcm-related configs.
174pub type XcmWeigher = WeightInfoBounds<
175	crate::weights::xcm::XcmWeight<Runtime, RuntimeCall>,
176	RuntimeCall,
177	MaxInstructions,
178>;
179
180pub type XcmBarrier = TrailingSetTopicAsId<(
181	// Weight that is paid for may be consumed.
182	TakeWeightCredit,
183	// Expected responses are OK.
184	AllowKnownQueryResponses<PolkadotXcm>,
185	WithComputedOrigin<
186		(
187			// If the message is one that immediately attemps to pay for execution, then allow it.
188			AllowTopLevelPaidExecutionFrom<Everything>,
189			// Subscriptions for version tracking are OK.
190			AllowSubscriptionsFrom<Everything>,
191		),
192		UniversalLocation,
193		ConstU32<8>,
194	>,
195)>;
196
197parameter_types! {
198	/// Xcm fees will go to the treasury account
199	pub XcmFeesAccount: AccountId = Treasury::account_id();
200}
201
202pub struct SafeCallFilter;
203impl frame_support::traits::Contains<RuntimeCall> for SafeCallFilter {
204	fn contains(_call: &RuntimeCall) -> bool {
205		// TODO review
206		// This needs to be addressed at EVM level
207		true
208	}
209}
210
211parameter_types! {
212	 /// Location of Asset Hub
213	pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]);
214	pub const RelayLocation: Location = Location::parent();
215	pub RelayLocationFilter: AssetFilter = Wild(AllOf {
216		fun: WildFungible,
217		id: xcm::prelude::AssetId(RelayLocation::get()),
218	});
219	pub RelayChainNativeAssetFromAssetHub: (AssetFilter, Location) = (
220		RelayLocationFilter::get(),
221		AssetHubLocation::get()
222	);
223	pub const MaxAssetsIntoHolding: u32 = xcm_primitives::MAX_ASSETS;
224}
225
226type Reserves = (
227	// Assets bridged from different consensus systems held in reserve on Asset Hub.
228	IsBridgedConcreteAssetFrom<AssetHubLocation>,
229	// Assets bridged from Moonriver
230	IsBridgedConcreteAssetFrom<bp_moonriver::GlobalConsensusLocation>,
231	// Relaychain (DOT) from Asset Hub
232	Case<RelayChainNativeAssetFromAssetHub>,
233	// Assets which the reserve is the same as the origin.
234	MultiNativeAsset<AbsoluteAndRelativeReserve<SelfLocationAbsolute>>,
235);
236
237// Our implementation of the Moonbeam Call
238// Attaches the right origin in case the call is made to pallet-ethereum-xcm
239#[cfg(not(feature = "evm-tracing"))]
240moonbeam_runtime_common::impl_moonbeam_xcm_call!();
241#[cfg(feature = "evm-tracing")]
242moonbeam_runtime_common::impl_moonbeam_xcm_call_tracing!();
243
244moonbeam_runtime_common::impl_evm_runner_precompile_or_eth_xcm!();
245
246pub struct XcmExecutorConfig;
247impl xcm_executor::Config for XcmExecutorConfig {
248	type RuntimeCall = RuntimeCall;
249	type XcmSender = XcmRouter;
250	// How to withdraw and deposit an asset.
251	type AssetTransactor = AssetTransactors;
252	type OriginConverter = XcmOriginToTransactDispatchOrigin;
253	// Filter to the reserve withdraw operations
254	// Whenever the reserve matches the relative or absolute value
255	// of our chain, we always return the relative reserve
256	type IsReserve = Reserves;
257	type IsTeleporter = (); // No teleport
258	type UniversalLocation = UniversalLocation;
259	type Barrier = XcmBarrier;
260	type Weigher = XcmWeigher;
261	// As trader we use the XcmWeightTrader pallet.
262	// For each foreign asset, the fee is computed based on its relative price (also
263	// stored in the XcmWeightTrader pallet) against the native asset.
264	// For the native asset fee is computed using WeightToFee implementation.
265	type Trader = pallet_xcm_weight_trader::Trader<Runtime>;
266	type ResponseHandler = PolkadotXcm;
267	type SubscriptionService = PolkadotXcm;
268	type AssetTrap = pallet_erc20_xcm_bridge::AssetTrapWrapper<PolkadotXcm, Runtime>;
269	type AssetClaims = PolkadotXcm;
270	type CallDispatcher = MoonbeamCall;
271	type PalletInstancesInfo = crate::AllPalletsWithSystem;
272	type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
273	type AssetLocker = ();
274	type AssetExchanger = ();
275	type FeeManager = ();
276	type MessageExporter = BridgeXcmOverMoonriver;
277	type UniversalAliases = bridge_config::UniversalAliases;
278	type SafeCallFilter = SafeCallFilter;
279	type Aliasers = Nothing;
280	type TransactionalProcessor = xcm_builder::FrameTransactionalProcessor;
281	type HrmpNewChannelOpenRequestHandler = ();
282	type HrmpChannelAcceptedHandler = ();
283	type HrmpChannelClosingHandler = ();
284	type XcmRecorder = PolkadotXcm;
285	type XcmEventEmitter = PolkadotXcm;
286}
287
288pub type XcmExecutor = pallet_erc20_xcm_bridge::XcmExecutorWrapper<
289	XcmExecutorConfig,
290	xcm_executor::XcmExecutor<XcmExecutorConfig>,
291>;
292
293// Converts a Signed Local Origin into a Location
294pub type LocalOriginToLocation = SignedToAccountId20<RuntimeOrigin, AccountId, RelayNetwork>;
295
296/// For routing XCM messages which do not cross local consensus boundary.
297pub type LocalXcmRouter = (
298	// Two routers - use UMP to communicate with the relay chain:
299	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
300	// ..and XCMP to communicate with the sibling chains.
301	XcmpQueue,
302);
303
304/// The means for routing XCM messages which are not for local execution into the right message
305/// queues.
306pub type XcmRouter = WithUniqueTopic<(
307	// The means for routing XCM messages which are not for local execution into the right message
308	// queues.
309	LocalXcmRouter,
310	// Router that exports messages to be delivered to the Kusama GlobalConsensus
311	moonbeam_runtime_common::bridge::BridgeXcmRouter<
312		xcm_builder::LocalExporter<BridgeXcmOverMoonriver, UniversalLocation>,
313	>,
314)>;
315
316impl pallet_xcm::Config for Runtime {
317	type RuntimeEvent = RuntimeEvent;
318	type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
319	type XcmRouter = XcmRouter;
320	type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
321	type XcmExecuteFilter = Nothing;
322	type XcmExecutor = XcmExecutor;
323	type XcmTeleportFilter = Nothing;
324	type XcmReserveTransferFilter = Everything;
325	type Weigher = XcmWeigher;
326	type UniversalLocation = UniversalLocation;
327	type RuntimeOrigin = RuntimeOrigin;
328	type RuntimeCall = RuntimeCall;
329	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
330	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
331	type Currency = Balances;
332	type CurrencyMatcher = ();
333	type TrustedLockers = ();
334	type SovereignAccountOf = LocationToAccountId;
335	type MaxLockers = ConstU32<8>;
336	type MaxRemoteLockConsumers = ConstU32<0>;
337	type RemoteLockConsumerIdentifier = ();
338	type WeightInfo = moonbeam_weights::pallet_xcm::WeightInfo<Runtime>;
339	type AdminOrigin = EnsureRoot<AccountId>;
340	type AuthorizedAliasConsideration = Disabled;
341}
342
343impl cumulus_pallet_xcm::Config for Runtime {
344	type RuntimeEvent = RuntimeEvent;
345	type XcmExecutor = XcmExecutor;
346}
347
348impl cumulus_pallet_xcmp_queue::Config for Runtime {
349	type RuntimeEvent = RuntimeEvent;
350	type ChannelInfo = ParachainSystem;
351	type VersionWrapper = PolkadotXcm;
352	type XcmpQueue = TransformOrigin<MessageQueue, AggregateMessageOrigin, ParaId, ParaIdToSibling>;
353	type MaxInboundSuspended = sp_core::ConstU32<1_000>;
354	type ControllerOrigin = EnsureRoot<AccountId>;
355	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
356	type WeightInfo = moonbeam_weights::cumulus_pallet_xcmp_queue::WeightInfo<Runtime>;
357	type PriceForSiblingDelivery = polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery<
358		cumulus_primitives_core::ParaId,
359	>;
360	type MaxActiveOutboundChannels = ConstU32<128>;
361	// Most on-chain HRMP channels are configured to use 102400 bytes of max message size, so we
362	// need to set the page size larger than that until we reduce the channel size on-chain.
363	type MaxPageSize = MessageQueueHeapSize;
364}
365
366parameter_types! {
367	pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent;
368}
369
370parameter_types! {
371	/// The amount of weight (if any) which should be provided to the message queue for
372	/// servicing enqueued items.
373	///
374	/// This may be legitimately `None` in the case that you will call
375	/// `ServiceQueues::service_queues` manually.
376	pub MessageQueueServiceWeight: Weight =
377		Perbill::from_percent(25) * RuntimeBlockWeights::get().max_block;
378	/// The maximum number of stale pages (i.e. of overweight messages) allowed before culling
379	/// can happen. Once there are more stale pages than this, then historical pages may be
380	/// dropped, even if they contain unprocessed overweight messages.
381	pub const MessageQueueMaxStale: u32 = 8;
382	/// The size of the page; this implies the maximum message size which can be sent.
383	///
384	/// A good value depends on the expected message sizes, their weights, the weight that is
385	/// available for processing them and the maximal needed message size. The maximal message
386	/// size is slightly lower than this as defined by [`MaxMessageLenOf`].
387	pub const MessageQueueHeapSize: u32 = 103 * 1024;
388}
389
390impl pallet_message_queue::Config for Runtime {
391	type RuntimeEvent = RuntimeEvent;
392	#[cfg(feature = "runtime-benchmarks")]
393	type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor<
394		cumulus_primitives_core::AggregateMessageOrigin,
395	>;
396	#[cfg(not(feature = "runtime-benchmarks"))]
397	type MessageProcessor = pallet_ethereum_xcm::MessageProcessorWrapper<
398		xcm_builder::ProcessXcmMessage<AggregateMessageOrigin, XcmExecutor, RuntimeCall>,
399	>;
400	type Size = u32;
401	type HeapSize = MessageQueueHeapSize;
402	type MaxStale = MessageQueueMaxStale;
403	type ServiceWeight = MessageQueueServiceWeight;
404	// The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin:
405	type QueueChangeHandler = NarrowOriginToSibling<XcmpQueue>;
406	// NarrowOriginToSibling calls XcmpQueue's is_paused if Origin is sibling. Allows all other origins
407	type QueuePausedQuery = EmergencyParaXcm;
408	type WeightInfo = moonbeam_weights::pallet_message_queue::WeightInfo<Runtime>;
409	type IdleMaxServiceWeight = MessageQueueServiceWeight;
410}
411
412pub type FastAuthorizeUpgradeOrigin = EitherOfDiverse<
413	EnsureRoot<AccountId>,
414	pallet_collective::EnsureProportionAtLeast<AccountId, OpenTechCommitteeInstance, 5, 9>,
415>;
416
417pub type ResumeXcmOrigin = EitherOfDiverse<
418	EnsureRoot<AccountId>,
419	pallet_collective::EnsureProportionAtLeast<AccountId, OpenTechCommitteeInstance, 5, 9>,
420>;
421
422impl pallet_emergency_para_xcm::Config for Runtime {
423	type CheckAssociatedRelayNumber =
424		cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases;
425	type QueuePausedQuery = (MaintenanceMode, NarrowOriginToSibling<XcmpQueue>);
426	type PausedThreshold = ConstU32<300>;
427	type FastAuthorizeUpgradeOrigin = FastAuthorizeUpgradeOrigin;
428	type PausedToNormalOrigin = ResumeXcmOrigin;
429}
430
431// Our AssetType. For now we only handle Xcm Assets
432#[derive(
433	Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, DecodeWithMemTracking,
434)]
435pub enum AssetType {
436	Xcm(xcm::v3::Location),
437}
438impl Default for AssetType {
439	fn default() -> Self {
440		Self::Xcm(xcm::v3::Location::here())
441	}
442}
443
444impl From<xcm::v3::Location> for AssetType {
445	fn from(location: xcm::v3::Location) -> Self {
446		Self::Xcm(location)
447	}
448}
449
450// This can be removed once we fully adopt xcm::v5 everywhere
451impl TryFrom<Location> for AssetType {
452	type Error = ();
453
454	fn try_from(location: Location) -> Result<Self, Self::Error> {
455		// Convert the V5 location to a V3 location
456		match xcm::VersionedLocation::V5(location).into_version(xcm::v3::VERSION) {
457			Ok(xcm::VersionedLocation::V3(loc)) => Ok(AssetType::Xcm(loc.into())),
458			// Any other version or conversion error returns an error
459			_ => Err(()),
460		}
461	}
462}
463
464impl Into<Option<xcm::v3::Location>> for AssetType {
465	fn into(self) -> Option<xcm::v3::Location> {
466		match self {
467			Self::Xcm(location) => Some(location),
468		}
469	}
470}
471
472impl Into<Option<Location>> for AssetType {
473	fn into(self) -> Option<Location> {
474		match self {
475			Self::Xcm(location) => {
476				let versioned = xcm::VersionedLocation::V3(location);
477				match versioned.into_version(xcm::latest::VERSION) {
478					Ok(xcm::VersionedLocation::V5(loc)) => Some(loc),
479					_ => None,
480				}
481			}
482		}
483	}
484}
485
486// Implementation on how to retrieve the AssetId from an AssetType
487// We simply hash the AssetType and take the lowest 128 bits
488impl From<AssetType> for AssetId {
489	fn from(asset: AssetType) -> AssetId {
490		match asset {
491			AssetType::Xcm(id) => {
492				let mut result: [u8; 16] = [0u8; 16];
493				let hash: H256 = id.using_encoded(<Runtime as frame_system::Config>::Hashing::hash);
494				result.copy_from_slice(&hash.as_fixed_bytes()[0..16]);
495				u128::from_le_bytes(result)
496			}
497		}
498	}
499}
500
501// Our currencyId. We distinguish for now between SelfReserve, and Others, defined by their Id.
502#[derive(
503	Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, DecodeWithMemTracking,
504)]
505pub enum CurrencyId {
506	// Our native token
507	SelfReserve,
508	// Assets representing other chains native tokens
509	ForeignAsset(AssetId),
510	// Erc20 token
511	Erc20 { contract_address: H160 },
512}
513
514impl AccountIdToCurrencyId<AccountId, CurrencyId> for Runtime {
515	fn account_to_currency_id(account: AccountId) -> Option<CurrencyId> {
516		Some(match account {
517			// the self-reserve currency is identified by the pallet-balances address
518			a if a == H160::from_low_u64_be(2050).into() => CurrencyId::SelfReserve,
519			// the rest of the currencies, by their corresponding erc20 address
520			_ => match Runtime::account_to_asset_id(account) {
521				// We distinguish by prefix, and depending on it we create either
522				// Foreign or Local
523				Some((_prefix, asset_id)) => CurrencyId::ForeignAsset(asset_id),
524				// If no known prefix is identified, we consider that it's a "real" erc20 token
525				// (i.e. managed by a real smart contract)
526				None => CurrencyId::Erc20 {
527					contract_address: account.into(),
528				},
529			},
530		})
531	}
532}
533// How to convert from CurrencyId to Location
534pub struct CurrencyIdToLocation<AssetXConverter>(sp_std::marker::PhantomData<AssetXConverter>);
535impl<AssetXConverter> sp_runtime::traits::Convert<CurrencyId, Option<Location>>
536	for CurrencyIdToLocation<AssetXConverter>
537where
538	AssetXConverter: MaybeEquivalence<Location, AssetId>,
539{
540	fn convert(currency: CurrencyId) -> Option<Location> {
541		match currency {
542			CurrencyId::SelfReserve => {
543				let multi: Location = SelfReserve::get();
544				Some(multi)
545			}
546			CurrencyId::ForeignAsset(asset) => AssetXConverter::convert_back(&asset),
547			CurrencyId::Erc20 { contract_address } => {
548				let mut location = Erc20XcmBridgePalletLocation::get();
549				location
550					.push_interior(Junction::AccountKey20 {
551						key: contract_address.0,
552						network: None,
553					})
554					.ok();
555				Some(location)
556			}
557		}
558	}
559}
560
561parameter_types! {
562	pub const BaseXcmWeight: Weight = Weight::from_parts(200_000_000u64, 0);
563	pub const MaxAssetsForTransfer: usize = 2;
564
565	// This is how we are going to detect whether the asset is a Reserve asset
566	// This however is the chain part only
567	pub SelfLocation: Location = Location::here();
568	// We need this to be able to catch when someone is trying to execute a non-
569	// cross-chain transfer in xtokens through the absolute path way
570	pub SelfLocationAbsolute: Location = Location {
571		parents:1,
572		interior: [
573			Parachain(ParachainInfo::parachain_id().into())
574		].into()
575	};
576}
577
578// 1 DOT should be enough
579parameter_types! {
580	pub MaxHrmpRelayFee: Asset = (Location::parent(), 1_000_000_000_000u128).into();
581}
582
583// For now we only allow to transact in the relay, although this might change in the future
584// Transactors just defines the chains in which we allow transactions to be issued through
585// xcm
586#[derive(
587	Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, DecodeWithMemTracking,
588)]
589pub enum Transactors {
590	Relay,
591	AssetHub,
592}
593
594// Default for benchmarking
595#[cfg(feature = "runtime-benchmarks")]
596impl Default for Transactors {
597	fn default() -> Self {
598		Transactors::Relay
599	}
600}
601
602impl TryFrom<u8> for Transactors {
603	type Error = ();
604	fn try_from(value: u8) -> Result<Self, Self::Error> {
605		match value {
606			0u8 => Ok(Transactors::Relay),
607			1u8 => Ok(Transactors::AssetHub),
608			_ => Err(()),
609		}
610	}
611}
612
613impl XcmTransact for Transactors {
614	fn destination(self) -> Location {
615		match self {
616			Transactors::Relay => RelayLocation::get(),
617			Transactors::AssetHub => AssetHubLocation::get(),
618		}
619	}
620
621	fn utility_pallet_index(&self) -> u8 {
622		match self {
623			Transactors::Relay => pallet_xcm_transactor::RelayIndices::<Runtime>::get().utility,
624			Transactors::AssetHub => pallet_xcm_transactor::ASSET_HUB_UTILITY_PALLET_INDEX,
625		}
626	}
627
628	fn staking_pallet_index(&self) -> u8 {
629		match self {
630			Transactors::Relay => pallet_xcm_transactor::RelayIndices::<Runtime>::get().staking,
631			Transactors::AssetHub => pallet_xcm_transactor::ASSET_HUB_STAKING_PALLET_INDEX,
632		}
633	}
634}
635
636pub type DerivativeAddressRegistrationOrigin =
637	EitherOfDiverse<EnsureRoot<AccountId>, governance::custom_origins::GeneralAdmin>;
638
639impl pallet_xcm_transactor::Config for Runtime {
640	type Balance = Balance;
641	type Transactor = Transactors;
642	type DerivativeAddressRegistrationOrigin = DerivativeAddressRegistrationOrigin;
643	type SovereignAccountDispatcherOrigin = EnsureRoot<AccountId>;
644	type CurrencyId = CurrencyId;
645	type AccountIdToLocation = AccountIdToLocation<AccountId>;
646	type CurrencyIdToLocation = CurrencyIdToLocation<(EvmForeignAssets,)>;
647	type XcmSender = XcmRouter;
648	type SelfLocation = SelfLocation;
649	type Weigher = XcmWeigher;
650	type UniversalLocation = UniversalLocation;
651	type BaseXcmWeight = BaseXcmWeight;
652	type AssetTransactor = AssetTransactors;
653	type ReserveProvider = AbsoluteAndRelativeReserve<SelfLocationAbsolute>;
654	type WeightInfo = moonbeam_weights::pallet_xcm_transactor::WeightInfo<Runtime>;
655	type HrmpManipulatorOrigin = GeneralAdminOrRoot;
656	type HrmpOpenOrigin = FastGeneralAdminOrRoot;
657	type MaxHrmpFee = xcm_builder::Case<MaxHrmpRelayFee>;
658	type FeeTrader = XcmWeightTrader;
659}
660
661parameter_types! {
662	// This is the relative view of erc20 assets.
663	// Identified by this prefix + AccountKey20(contractAddress)
664	// We use the RELATIVE multilocation
665	pub Erc20XcmBridgePalletLocation: Location = Location {
666		parents:0,
667		interior: [
668			PalletInstance(<Erc20XcmBridge as PalletInfoAccess>::index() as u8)
669		].into()
670	};
671
672	// To be able to support almost all erc20 implementations,
673	// we provide a sufficiently hight gas limit.
674	pub Erc20XcmBridgeTransferGasLimit: u64 = 400_000;
675}
676
677impl pallet_erc20_xcm_bridge::Config for Runtime {
678	type AccountIdConverter = LocationToH160;
679	type Erc20MultilocationPrefix = Erc20XcmBridgePalletLocation;
680	type Erc20TransferGasLimit = Erc20XcmBridgeTransferGasLimit;
681	type EvmRunner = EvmRunnerPrecompileOrEthXcm<MoonbeamCall, Self>;
682}
683
684pub struct AccountIdToH160;
685impl sp_runtime::traits::Convert<AccountId, H160> for AccountIdToH160 {
686	fn convert(account_id: AccountId) -> H160 {
687		account_id.into()
688	}
689}
690
691pub type ForeignAssetManagerOrigin = EitherOf<
692	MapSuccessToXcm<EnsureXcm<AllowSiblingParachains>>,
693	MapSuccessToGovernance<
694		EitherOf<
695			EnsureRoot<AccountId>,
696			EitherOf<
697				pallet_collective::EnsureProportionMoreThan<
698					AccountId,
699					OpenTechCommitteeInstance,
700					5,
701					9,
702				>,
703				EitherOf<
704					governance::custom_origins::FastGeneralAdmin,
705					governance::custom_origins::GeneralAdmin,
706				>,
707			>,
708		>,
709	>,
710>;
711
712impl pallet_moonbeam_foreign_assets::Config for Runtime {
713	type AccountIdToH160 = AccountIdToH160;
714	type AssetIdFilter = Everything;
715	type EvmRunner = EvmRunnerPrecompileOrEthXcm<MoonbeamCall, Self>;
716	type ConvertLocation =
717		SiblingParachainConvertsVia<polkadot_parachain::primitives::Sibling, AccountId>;
718	type ForeignAssetCreatorOrigin = ForeignAssetManagerOrigin;
719	type ForeignAssetFreezerOrigin = ForeignAssetManagerOrigin;
720	type ForeignAssetModifierOrigin = ForeignAssetManagerOrigin;
721	type ForeignAssetUnfreezerOrigin = ForeignAssetManagerOrigin;
722	type OnForeignAssetCreated = ();
723	type MaxForeignAssets = ConstU32<256>;
724	type WeightInfo = moonbeam_weights::pallet_moonbeam_foreign_assets::WeightInfo<Runtime>;
725	type XcmLocationToH160 = LocationToH160;
726	type ForeignAssetCreationDeposit = dynamic_params::xcm_config::ForeignAssetCreationDeposit;
727	type Balance = Balance;
728	type Currency = Balances;
729}
730
731pub struct AssetFeesFilter;
732impl frame_support::traits::Contains<Location> for AssetFeesFilter {
733	fn contains(location: &Location) -> bool {
734		location.parent_count() > 0
735			&& location.first_interior() != Erc20XcmBridgePalletLocation::get().first_interior()
736	}
737}
738
739pub type AddAndEditSupportedAssetOrigin = EitherOfDiverse<
740	EnsureRoot<AccountId>,
741	EitherOfDiverse<
742		pallet_collective::EnsureProportionMoreThan<AccountId, OpenTechCommitteeInstance, 5, 9>,
743		EitherOf<
744			governance::custom_origins::GeneralAdmin,
745			governance::custom_origins::FastGeneralAdmin,
746		>,
747	>,
748>;
749
750pub type RemoveSupportedAssetOrigin = EitherOfDiverse<
751	EnsureRoot<AccountId>,
752	pallet_collective::EnsureProportionMoreThan<AccountId, OpenTechCommitteeInstance, 5, 9>,
753>;
754
755impl pallet_xcm_weight_trader::Config for Runtime {
756	type AccountIdToLocation = AccountIdToLocation<AccountId>;
757	type AddSupportedAssetOrigin = AddAndEditSupportedAssetOrigin;
758	type AssetLocationFilter = AssetFeesFilter;
759	type AssetTransactor = AssetTransactors;
760	type Balance = Balance;
761	type EditSupportedAssetOrigin = AddAndEditSupportedAssetOrigin;
762	type NativeLocation = SelfReserve;
763	type PauseSupportedAssetOrigin = AddAndEditSupportedAssetOrigin;
764	type ResumeSupportedAssetOrigin = AddAndEditSupportedAssetOrigin;
765	type RemoveSupportedAssetOrigin = RemoveSupportedAssetOrigin;
766	type WeightInfo = moonbeam_weights::pallet_xcm_weight_trader::WeightInfo<Runtime>;
767	type WeightToFee = <Runtime as pallet_transaction_payment::Config>::WeightToFee;
768	type XcmFeesAccount = XcmFeesAccount;
769	#[cfg(feature = "runtime-benchmarks")]
770	type NotFilteredLocation = RelayLocation;
771}
772
773#[cfg(feature = "runtime-benchmarks")]
774mod testing {
775	use super::*;
776
777	/// This From exists for benchmarking purposes. It has the potential side-effect of calling
778	/// EvmForeignAssets::set_asset() and should NOT be used in any production code.
779	impl From<Location> for CurrencyId {
780		fn from(location: Location) -> CurrencyId {
781			use sp_runtime::traits::MaybeEquivalence;
782
783			// If it does not exist, for benchmarking purposes, we create the association
784			let asset_id = if let Some(asset_id) = EvmForeignAssets::convert(&location) {
785				asset_id
786			} else {
787				// Generate asset ID from location hash (similar to old AssetManager approach)
788				let hash: H256 =
789					location.using_encoded(<Runtime as frame_system::Config>::Hashing::hash);
790				let mut result: [u8; 16] = [0u8; 16];
791				result.copy_from_slice(&hash.as_fixed_bytes()[0..16]);
792				let asset_id = u128::from_le_bytes(result);
793
794				EvmForeignAssets::set_asset(location, asset_id);
795				asset_id
796			};
797
798			CurrencyId::ForeignAsset(asset_id)
799		}
800	}
801}