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::{
24
	dispatch::{GetDispatchInfo, PostDispatchInfo},
25
	traits::OriginTrait,
26
};
27
use pallet_evm::AddressMapping;
28
use parity_scale_codec::{Decode, DecodeLimit, MaxEncodedLen};
29
use precompile_utils::precompile_set::SelectorFilter;
30
use precompile_utils::prelude::*;
31
use sp_core::{H160, U256};
32
use sp_runtime::traits::Dispatchable;
33
use sp_std::boxed::Box;
34
use sp_std::marker::PhantomData;
35
use sp_std::vec;
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
use xcm_executor::traits::WeightTrader;
42

            
43
use xcm_primitives::DEFAULT_PROOF_SIZE;
44

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

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

            
55
#[cfg(test)]
56
mod mock;
57
#[cfg(test)]
58
mod tests;
59

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

            
63
impl<Runtime, XcmConfig> SelectorFilter for AllExceptXcmExecute<Runtime, XcmConfig>
64
where
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
1
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
77
1
		match selector {
78
			None => true,
79
1
			Some(selector) => {
80
1
				!XcmUtilsPrecompileCall::<Runtime, XcmConfig>::xcm_execute_selectors()
81
1
					.contains(&selector)
82
			}
83
		}
84
1
	}
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
92
pub struct XcmUtilsPrecompile<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
93

            
94
155
#[precompile_utils::precompile]
95
impl<Runtime, XcmConfig> XcmUtilsPrecompile<Runtime, XcmConfig>
96
where
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
11
	fn multilocation_to_address(
111
11
		handle: &mut impl PrecompileHandle,
112
11
		location: Location,
113
11
	) -> EvmResult<Address> {
114
11
		// storage item: AssetTypeUnitsPerSecond
115
11
		// max encoded len: hash (16) + Multilocation + u128 (16)
116
11
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
117

            
118
11
		let origin =
119
11
			XcmConfig::OriginConverter::convert_origin(location, OriginKind::SovereignAccount)
120
11
				.map_err(|_| {
121
					RevertReason::custom("Failed multilocation conversion").in_field("location")
122
11
				})?;
123

            
124
11
		let account: H160 = origin
125
11
			.into_signer()
126
11
			.ok_or(
127
11
				RevertReason::custom("Failed multilocation conversion").in_field("multilocation"),
128
11
			)?
129
11
			.into();
130
11
		Ok(Address(account))
131
11
	}
132

            
133
	#[precompile::public("getUnitsPerSecond((uint8,bytes[]))")]
134
	#[precompile::view]
135
4
	fn get_units_per_second(
136
4
		handle: &mut impl PrecompileHandle,
137
4
		location: Location,
138
4
	) -> EvmResult<U256> {
139
4
		// storage item: AssetTypeUnitsPerSecond
140
4
		// max encoded len: hash (16) + Multilocation + u128 (16)
141
4
		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
4
		let multiasset: xcm::latest::Asset = (location.clone(), u128::MAX).into();
146
4
		let weight_per_second = 1_000_000_000_000u64;
147
4

            
148
4
		let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
149
4

            
150
4
		let ctx = XcmContext {
151
4
			origin: Some(location),
152
4
			message_id: XcmHash::default(),
153
4
			topic: None,
154
4
		};
155
		// buy_weight returns unused assets
156
4
		let unused = trader
157
4
			.buy_weight(
158
4
				Weight::from_parts(weight_per_second, DEFAULT_PROOF_SIZE),
159
4
				vec![multiasset.clone()].into(),
160
4
				&ctx,
161
4
			)
162
4
			.map_err(|_| {
163
				RevertReason::custom("Asset not supported as fee payment").in_field("multilocation")
164
4
			})?;
165

            
166
		// we just need to substract from u128::MAX the unused assets
167
4
		if let Some(amount) = unused
168
4
			.fungible
169
4
			.get(&multiasset.id)
170
4
			.map(|&value| u128::MAX.saturating_sub(value))
171
		{
172
4
			Ok(amount.into())
173
		} else {
174
			Err(revert(
175
				"Weight was too expensive to be bought with this asset",
176
			))
177
		}
178
4
	}
179

            
180
	#[precompile::public("weightMessage(bytes)")]
181
	#[precompile::view]
182
4
	fn weight_message(
183
4
		_handle: &mut impl PrecompileHandle,
184
4
		message: BoundedBytes<GetXcmSizeLimit>,
185
4
	) -> EvmResult<u64> {
186
4
		let message: Vec<u8> = message.into();
187
4

            
188
4
		let msg =
189
4
			VersionedXcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::decode_all_with_depth_limit(
190
4
				MAX_XCM_DECODE_DEPTH,
191
4
				&mut message.as_slice(),
192
4
			)
193
4
			.map(Xcm::<<XcmConfig as xcm_executor::Config>::RuntimeCall>::try_from);
194

            
195
4
		let result = match msg {
196
4
			Ok(Ok(mut x)) => {
197
4
				XcmConfig::Weigher::weight(&mut x).map_err(|_| revert("failed weighting"))
198
			}
199
			_ => Err(RevertReason::custom("Failed decoding")
200
				.in_field("message")
201
				.into()),
202
		};
203

            
204
4
		Ok(result?.ref_time())
205
4
	}
206

            
207
	#[precompile::public("xcmExecute(bytes,uint64)")]
208
3
	fn xcm_execute(
209
3
		handle: &mut impl PrecompileHandle,
210
3
		message: BoundedBytes<GetXcmSizeLimit>,
211
3
		weight: u64,
212
3
	) -> EvmResult {
213
3
		let message: Vec<u8> = message.into();
214
3

            
215
3
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
216
3

            
217
3
		let message: Vec<_> = message.to_vec();
218
3
		let xcm = xcm::VersionedXcm::<CallOf<Runtime>>::decode_all_with_depth_limit(
219
3
			xcm::MAX_XCM_DECODE_DEPTH,
220
3
			&mut message.as_slice(),
221
3
		)
222
3
		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
223

            
224
3
		let call = pallet_xcm::Call::<Runtime>::execute {
225
3
			message: Box::new(xcm),
226
3
			max_weight: Weight::from_parts(weight, DEFAULT_PROOF_SIZE),
227
3
		};
228
3

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

            
231
3
		Ok(())
232
3
	}
233

            
234
	#[precompile::public("xcmSend((uint8,bytes[]),bytes)")]
235
1
	fn xcm_send(
236
1
		handle: &mut impl PrecompileHandle,
237
1
		dest: Location,
238
1
		message: BoundedBytes<GetXcmSizeLimit>,
239
1
	) -> EvmResult {
240
1
		let message: Vec<u8> = message.into();
241
1

            
242
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
243
1

            
244
1
		let message: Vec<_> = message.to_vec();
245
1
		let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit(
246
1
			xcm::MAX_XCM_DECODE_DEPTH,
247
1
			&mut message.as_slice(),
248
1
		)
249
1
		.map_err(|_e| RevertReason::custom("Failed xcm decoding").in_field("message"))?;
250

            
251
1
		let call = pallet_xcm::Call::<Runtime>::send {
252
1
			dest: Box::new(dest.into()),
253
1
			message: Box::new(xcm),
254
1
		};
255
1

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

            
258
1
		Ok(())
259
1
	}
260
}