pallet_evm_precompile_xcm_utils/
lib.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//! Precompile to xcm utils runtime methods via the EVM
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21use fp_evm::PrecompileHandle;
22use frame_support::traits::ConstU32;
23use frame_support::{
24	dispatch::{GetDispatchInfo, PostDispatchInfo},
25	traits::OriginTrait,
26};
27use pallet_evm::AddressMapping;
28use parity_scale_codec::{Decode, DecodeLimit, MaxEncodedLen};
29use precompile_utils::precompile_set::SelectorFilter;
30use precompile_utils::prelude::*;
31use sp_core::{H160, U256};
32use sp_runtime::traits::Dispatchable;
33use sp_std::boxed::Box;
34use sp_std::marker::PhantomData;
35use sp_std::vec;
36use sp_std::vec::Vec;
37use sp_weights::Weight;
38use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH};
39use xcm_executor::traits::ConvertOrigin;
40use xcm_executor::traits::WeightBounds;
41use xcm_executor::traits::WeightTrader;
42
43use xcm_primitives::DEFAULT_PROOF_SIZE;
44
45pub type XcmOriginOf<XcmConfig> =
46	<<XcmConfig as xcm_executor::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin;
47pub type XcmAccountIdOf<XcmConfig> =
48	<<<XcmConfig as xcm_executor::Config>::RuntimeCall as Dispatchable>
49		::RuntimeOrigin as OriginTrait>::AccountId;
50
51pub type CallOf<Runtime> = <Runtime as pallet_xcm::Config>::RuntimeCall;
52pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
53type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;
54
55#[cfg(test)]
56mod mock;
57#[cfg(test)]
58mod tests;
59
60#[derive(Debug)]
61pub struct AllExceptXcmExecute<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
62
63impl<Runtime, XcmConfig> SelectorFilter for AllExceptXcmExecute<Runtime, XcmConfig>
64where
65	Runtime: pallet_evm::Config + frame_system::Config + pallet_xcm::Config,
66	XcmOriginOf<XcmConfig>: OriginTrait,
67	XcmAccountIdOf<XcmConfig>: Into<H160>,
68	XcmConfig: xcm_executor::Config,
69	<Runtime as frame_system::Config>::RuntimeCall:
70		Dispatchable<PostInfo = PostDispatchInfo> + Decode + GetDispatchInfo,
71	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
72		From<Option<Runtime::AccountId>>,
73	<Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>,
74	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
75{
76	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
77		match selector {
78			None => true,
79			Some(selector) => {
80				!XcmUtilsPrecompileCall::<Runtime, XcmConfig>::xcm_execute_selectors()
81					.contains(&selector)
82			}
83		}
84	}
85
86	fn description() -> String {
87		"Allowed for all callers for all selectors except 'execute'".into()
88	}
89}
90
91/// A precompile to wrap the functionality from xcm-utils
92pub struct XcmUtilsPrecompile<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
93
94#[precompile_utils::precompile]
95impl<Runtime, XcmConfig> XcmUtilsPrecompile<Runtime, XcmConfig>
96where
97	Runtime: pallet_evm::Config + frame_system::Config + pallet_xcm::Config,
98	XcmOriginOf<XcmConfig>: OriginTrait,
99	XcmAccountIdOf<XcmConfig>: Into<H160>,
100	XcmConfig: xcm_executor::Config,
101	<Runtime as frame_system::Config>::RuntimeCall:
102		Dispatchable<PostInfo = PostDispatchInfo> + Decode + GetDispatchInfo,
103	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
104		From<Option<Runtime::AccountId>>,
105	<Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>,
106	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
107{
108	#[precompile::public("multilocationToAddress((uint8,bytes[]))")]
109	#[precompile::view]
110	fn multilocation_to_address(
111		handle: &mut impl PrecompileHandle,
112		location: Location,
113	) -> EvmResult<Address> {
114		// storage item: AssetTypeUnitsPerSecond
115		// max encoded len: hash (16) + Multilocation + u128 (16)
116		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
117
118		let origin =
119			XcmConfig::OriginConverter::convert_origin(location, OriginKind::SovereignAccount)
120				.map_err(|_| {
121					RevertReason::custom("Failed multilocation conversion").in_field("location")
122				})?;
123
124		let account: H160 = origin
125			.into_signer()
126			.ok_or(
127				RevertReason::custom("Failed multilocation conversion").in_field("multilocation"),
128			)?
129			.into();
130		Ok(Address(account))
131	}
132
133	#[precompile::public("getUnitsPerSecond((uint8,bytes[]))")]
134	#[precompile::view]
135	fn get_units_per_second(
136		handle: &mut impl PrecompileHandle,
137		location: Location,
138	) -> EvmResult<U256> {
139		// storage item: AssetTypeUnitsPerSecond
140		// max encoded len: hash (16) + Multilocation + u128 (16)
141		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
142
143		// We will construct an asset with the max amount, and check how much we
144		// get in return to substract
145		let multiasset: xcm::latest::Asset = (location.clone(), u128::MAX).into();
146		let weight_per_second = 1_000_000_000_000u64;
147
148		let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
149
150		let ctx = XcmContext {
151			origin: Some(location),
152			message_id: XcmHash::default(),
153			topic: None,
154		};
155		// buy_weight returns unused assets
156		let unused = trader
157			.buy_weight(
158				Weight::from_parts(weight_per_second, DEFAULT_PROOF_SIZE),
159				vec![multiasset.clone()].into(),
160				&ctx,
161			)
162			.map_err(|_| {
163				RevertReason::custom("Asset not supported as fee payment").in_field("multilocation")
164			})?;
165
166		// we just need to substract from u128::MAX the unused assets
167		if let Some(amount) = unused
168			.fungible
169			.get(&multiasset.id)
170			.map(|&value| u128::MAX.saturating_sub(value))
171		{
172			Ok(amount.into())
173		} else {
174			Err(revert(
175				"Weight was too expensive to be bought with this asset",
176			))
177		}
178	}
179
180	#[precompile::public("weightMessage(bytes)")]
181	#[precompile::view]
182	fn weight_message(
183		_handle: &mut impl PrecompileHandle,
184		message: BoundedBytes<GetXcmSizeLimit>,
185	) -> EvmResult<u64> {
186		let message: Vec<u8> = message.into();
187
188		let msg =
189			VersionedXcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::decode_all_with_depth_limit(
190				MAX_XCM_DECODE_DEPTH,
191				&mut message.as_slice(),
192			)
193			.map(Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::try_from);
194
195		let result = match msg {
196			Ok(Ok(mut x)) => XcmConfig::Weigher::weight(&mut x, Weight::MAX)
197				.map_err(|_| revert("failed weighting")),
198			_ => Err(RevertReason::custom("Failed decoding")
199				.in_field("message")
200				.into()),
201		};
202
203		Ok(result?.ref_time())
204	}
205
206	#[precompile::public("xcmExecute(bytes,uint64)")]
207	fn xcm_execute(
208		handle: &mut impl PrecompileHandle,
209		message: BoundedBytes<GetXcmSizeLimit>,
210		weight: u64,
211	) -> EvmResult {
212		let message: Vec<u8> = message.into();
213
214		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
215
216		let message: Vec<_> = message.to_vec();
217		let xcm = xcm::VersionedXcm::<CallOf<Runtime>>::decode_all_with_depth_limit(
218			xcm::MAX_XCM_DECODE_DEPTH,
219			&mut message.as_slice(),
220		)
221		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
222
223		let call = pallet_xcm::Call::<Runtime>::execute {
224			message: Box::new(xcm),
225			max_weight: Weight::from_parts(weight, DEFAULT_PROOF_SIZE),
226		};
227
228		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
229
230		Ok(())
231	}
232
233	#[precompile::public("xcmSend((uint8,bytes[]),bytes)")]
234	fn xcm_send(
235		handle: &mut impl PrecompileHandle,
236		dest: Location,
237		message: BoundedBytes<GetXcmSizeLimit>,
238	) -> EvmResult {
239		let message: Vec<u8> = message.into();
240
241		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
242
243		let message: Vec<_> = message.to_vec();
244		let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit(
245			xcm::MAX_XCM_DECODE_DEPTH,
246			&mut message.as_slice(),
247		)
248		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
249
250		let call = pallet_xcm::Call::<Runtime>::send {
251			dest: Box::new(dest.into()),
252			message: Box::new(xcm),
253		};
254
255		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
256
257		Ok(())
258	}
259}