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

            
21
use fp_evm::PrecompileHandle;
22
use frame_support::traits::ConstU32;
23
use frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND;
24
use frame_support::{
25
	dispatch::{GetDispatchInfo, PostDispatchInfo},
26
	traits::OriginTrait,
27
};
28
use pallet_evm::AddressMapping;
29
use parity_scale_codec::{Decode, DecodeLimit, MaxEncodedLen};
30
use precompile_utils::precompile_set::SelectorFilter;
31
use precompile_utils::prelude::*;
32
use sp_core::{H160, U256};
33
use sp_runtime::traits::Dispatchable;
34
use sp_std::boxed::Box;
35
use sp_std::marker::PhantomData;
36
use sp_std::vec::Vec;
37
use sp_weights::Weight;
38
use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH};
39
use xcm_executor::traits::ConvertOrigin;
40
use xcm_executor::traits::WeightBounds;
41

            
42
use xcm_primitives::DEFAULT_PROOF_SIZE;
43

            
44
// Import the pure fee computation function from the weight trader pallet
45
use pallet_xcm_weight_trader::compute_fee_amount;
46

            
47
pub type XcmOriginOf<XcmConfig> =
48
	<<XcmConfig as xcm_executor::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin;
49
pub type XcmAccountIdOf<XcmConfig> =
50
	<<<XcmConfig as xcm_executor::Config>::RuntimeCall as Dispatchable>
51
		::RuntimeOrigin as OriginTrait>::AccountId;
52

            
53
pub type CallOf<Runtime> = <Runtime as pallet_xcm::Config>::RuntimeCall;
54
pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
55
type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;
56

            
57
#[cfg(test)]
58
mod mock;
59
#[cfg(test)]
60
mod tests;
61

            
62
#[derive(Debug)]
63
pub struct AllExceptXcmExecute<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
64

            
65
impl<Runtime, XcmConfig> SelectorFilter for AllExceptXcmExecute<Runtime, XcmConfig>
66
where
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
1
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
82
1
		match selector {
83
			None => true,
84
1
			Some(selector) => {
85
1
				!XcmUtilsPrecompileCall::<Runtime, XcmConfig>::xcm_execute_selectors()
86
1
					.contains(&selector)
87
			}
88
		}
89
1
	}
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
97
pub struct XcmUtilsPrecompile<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
98

            
99
9
#[precompile_utils::precompile]
100
impl<Runtime, XcmConfig> XcmUtilsPrecompile<Runtime, XcmConfig>
101
where
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
11
	fn multilocation_to_address(
119
11
		handle: &mut impl PrecompileHandle,
120
11
		location: Location,
121
11
	) -> EvmResult<Address> {
122
		// storage item: AssetTypeUnitsPerSecond
123
		// max encoded len: hash (16) + Multilocation + u128 (16)
124
11
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
125

            
126
11
		let origin =
127
11
			XcmConfig::OriginConverter::convert_origin(location, OriginKind::SovereignAccount)
128
11
				.map_err(|_| {
129
					RevertReason::custom("Failed multilocation conversion").in_field("location")
130
				})?;
131

            
132
11
		let account: H160 = origin
133
11
			.into_signer()
134
11
			.ok_or(
135
11
				RevertReason::custom("Failed multilocation conversion").in_field("multilocation"),
136
			)?
137
11
			.into();
138
11
		Ok(Address(account))
139
11
	}
140

            
141
	#[precompile::public("getUnitsPerSecond((uint8,bytes[]))")]
142
	#[precompile::view]
143
4
	fn get_units_per_second(
144
4
		handle: &mut impl PrecompileHandle,
145
4
		location: Location,
146
4
	) -> EvmResult<U256> {
147
		// storage item: AssetTypeUnitsPerSecond
148
		// max encoded len: hash (16) + Multilocation + u128 (16)
149
4
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
150

            
151
4
		let weight_per_second = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, DEFAULT_PROOF_SIZE);
152

            
153
4
		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
4
		Ok(amount.into())
158
4
	}
159

            
160
	#[precompile::public("weightMessage(bytes)")]
161
	#[precompile::view]
162
4
	fn weight_message(
163
4
		_handle: &mut impl PrecompileHandle,
164
4
		message: BoundedBytes<GetXcmSizeLimit>,
165
4
	) -> EvmResult<u64> {
166
4
		let message: Vec<u8> = message.into();
167

            
168
4
		let msg =
169
4
			VersionedXcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::decode_all_with_depth_limit(
170
				MAX_XCM_DECODE_DEPTH,
171
4
				&mut message.as_slice(),
172
			)
173
4
			.map(Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::try_from);
174

            
175
4
		let result = match msg {
176
4
			Ok(Ok(mut x)) => XcmConfig::Weigher::weight(&mut x, Weight::MAX)
177
4
				.map_err(|_| revert("failed weighting")),
178
			_ => Err(RevertReason::custom("Failed decoding")
179
				.in_field("message")
180
				.into()),
181
		};
182

            
183
4
		Ok(result?.ref_time())
184
4
	}
185

            
186
	#[precompile::public("xcmExecute(bytes,uint64)")]
187
3
	fn xcm_execute(
188
3
		handle: &mut impl PrecompileHandle,
189
3
		message: BoundedBytes<GetXcmSizeLimit>,
190
3
		weight: u64,
191
3
	) -> EvmResult {
192
3
		let message: Vec<u8> = message.into();
193

            
194
3
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
195

            
196
3
		let message: Vec<_> = message.to_vec();
197
3
		let xcm = xcm::VersionedXcm::<CallOf<Runtime>>::decode_all_with_depth_limit(
198
			xcm::MAX_XCM_DECODE_DEPTH,
199
3
			&mut message.as_slice(),
200
		)
201
3
		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
202

            
203
3
		let call = pallet_xcm::Call::<Runtime>::execute {
204
3
			message: Box::new(xcm),
205
3
			max_weight: Weight::from_parts(weight, DEFAULT_PROOF_SIZE),
206
3
		};
207

            
208
3
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
209

            
210
3
		Ok(())
211
3
	}
212

            
213
	#[precompile::public("xcmSend((uint8,bytes[]),bytes)")]
214
1
	fn xcm_send(
215
1
		handle: &mut impl PrecompileHandle,
216
1
		dest: Location,
217
1
		message: BoundedBytes<GetXcmSizeLimit>,
218
1
	) -> EvmResult {
219
1
		let message: Vec<u8> = message.into();
220

            
221
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
222

            
223
1
		let message: Vec<_> = message.to_vec();
224
1
		let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit(
225
			xcm::MAX_XCM_DECODE_DEPTH,
226
1
			&mut message.as_slice(),
227
		)
228
1
		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
229

            
230
1
		let call = pallet_xcm::Call::<Runtime>::send {
231
1
			dest: Box::new(dest.into()),
232
1
			message: Box::new(xcm),
233
1
		};
234

            
235
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
236

            
237
1
		Ok(())
238
1
	}
239
}