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