1
// Copyright 2019-2022 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
{
75
33
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
76
33
		match selector {
77
3
			None => true,
78
30
			Some(selector) => {
79
30
				!XcmUtilsPrecompileCall::<Runtime, XcmConfig>::xcm_execute_selectors()
80
30
					.contains(&selector)
81
			}
82
		}
83
33
	}
84

            
85
	fn description() -> String {
86
		"Allowed for all callers for all selectors except 'execute'".into()
87
	}
88
}
89

            
90
/// A precompile to wrap the functionality from xcm-utils
91
pub struct XcmUtilsPrecompile<Runtime, XcmConfig>(PhantomData<(Runtime, XcmConfig)>);
92

            
93
161
#[precompile_utils::precompile]
94
impl<Runtime, XcmConfig> XcmUtilsPrecompile<Runtime, XcmConfig>
95
where
96
	Runtime: pallet_evm::Config + frame_system::Config + pallet_xcm::Config,
97
	XcmOriginOf<XcmConfig>: OriginTrait,
98
	XcmAccountIdOf<XcmConfig>: Into<H160>,
99
	XcmConfig: xcm_executor::Config,
100
	<Runtime as frame_system::Config>::RuntimeCall:
101
		Dispatchable<PostInfo = PostDispatchInfo> + Decode + GetDispatchInfo,
102
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
103
		From<Option<Runtime::AccountId>>,
104
	<Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>,
105
{
106
	#[precompile::public("multilocationToAddress((uint8,bytes[]))")]
107
	#[precompile::view]
108
11
	fn multilocation_to_address(
109
11
		handle: &mut impl PrecompileHandle,
110
11
		location: Location,
111
11
	) -> EvmResult<Address> {
112
11
		// storage item: AssetTypeUnitsPerSecond
113
11
		// max encoded len: hash (16) + Multilocation + u128 (16)
114
11
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
115

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

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

            
131
	#[precompile::public("getUnitsPerSecond((uint8,bytes[]))")]
132
	#[precompile::view]
133
4
	fn get_units_per_second(
134
4
		handle: &mut impl PrecompileHandle,
135
4
		location: Location,
136
4
	) -> EvmResult<U256> {
137
4
		// storage item: AssetTypeUnitsPerSecond
138
4
		// max encoded len: hash (16) + Multilocation + u128 (16)
139
4
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
140

            
141
		// We will construct an asset with the max amount, and check how much we
142
		// get in return to substract
143
4
		let multiasset: xcm::latest::Asset = (location.clone(), u128::MAX).into();
144
4
		let weight_per_second = 1_000_000_000_000u64;
145
4

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

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

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

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

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

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

            
202
4
		Ok(result?.ref_time())
203
4
	}
204

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

            
213
3
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
214
3

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

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

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

            
229
3
		Ok(())
230
3
	}
231

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

            
240
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
241
1

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

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

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

            
256
1
		Ok(())
257
1
	}
258
}