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
//! # Ethereum Xcm pallet
18
//!
19
//! The Xcm Ethereum pallet is a bridge for Xcm Transact to Ethereum pallet
20

            
21
// Ensure we're `no_std` when compiling for Wasm.
22
#![cfg_attr(not(feature = "std"), no_std)]
23
#![allow(clippy::comparison_chain, clippy::large_enum_variant)]
24

            
25
#[cfg(all(feature = "std", test))]
26
mod mock;
27
#[cfg(all(feature = "std", test))]
28
mod tests;
29

            
30
use ethereum_types::{H160, H256, U256};
31
use fp_ethereum::{TransactionData, ValidatedTransaction};
32
use fp_evm::{CheckEvmTransaction, CheckEvmTransactionConfig, TransactionValidationError};
33
use frame_support::{
34
	dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
35
	traits::{EnsureOrigin, Get, ProcessMessage},
36
	weights::Weight,
37
};
38
use frame_system::pallet_prelude::OriginFor;
39
use pallet_evm::{AddressMapping, GasWeightMapping};
40
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
41
use scale_info::TypeInfo;
42
use sp_runtime::{traits::UniqueSaturatedInto, DispatchErrorWithPostInfo, RuntimeDebug};
43
use sp_std::{marker::PhantomData, prelude::*};
44

            
45
pub use ethereum::{
46
	AccessListItem, BlockV2 as Block, LegacyTransactionMessage, Log, ReceiptV3 as Receipt,
47
	TransactionAction, TransactionV2 as Transaction,
48
};
49
pub use fp_rpc::TransactionStatus;
50
pub use xcm_primitives::{EnsureProxy, EthereumXcmTransaction, XcmToEthereum};
51

            
52
51
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
53
pub enum RawOrigin {
54
	XcmEthereumTransaction(H160),
55
}
56

            
57
pub fn ensure_xcm_ethereum_transaction<OuterOrigin>(o: OuterOrigin) -> Result<H160, &'static str>
58
where
59
	OuterOrigin: Into<Result<RawOrigin, OuterOrigin>>,
60
{
61
	match o.into() {
62
		Ok(RawOrigin::XcmEthereumTransaction(n)) => Ok(n),
63
		_ => Err("bad origin: expected to be a xcm Ethereum transaction"),
64
	}
65
}
66

            
67
pub struct EnsureXcmEthereumTransaction;
68
impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O>
69
	for EnsureXcmEthereumTransaction
70
{
71
	type Success = H160;
72
129
	fn try_origin(o: O) -> Result<Self::Success, O> {
73
129
		o.into().map(|o| match o {
74
129
			RawOrigin::XcmEthereumTransaction(id) => id,
75
129
		})
76
129
	}
77

            
78
	#[cfg(feature = "runtime-benchmarks")]
79
	fn try_successful_origin() -> Result<O, ()> {
80
		Ok(O::from(RawOrigin::XcmEthereumTransaction(
81
			Default::default(),
82
		)))
83
	}
84
}
85

            
86
68
environmental::environmental!(XCM_MESSAGE_HASH: H256);
87

            
88
pub struct MessageProcessorWrapper<Inner>(core::marker::PhantomData<Inner>);
89
impl<Inner: ProcessMessage> ProcessMessage for MessageProcessorWrapper<Inner> {
90
	type Origin = <Inner as ProcessMessage>::Origin;
91

            
92
	fn process_message(
93
		message: &[u8],
94
		origin: Self::Origin,
95
		meter: &mut frame_support::weights::WeightMeter,
96
		id: &mut [u8; 32],
97
	) -> Result<bool, frame_support::traits::ProcessMessageError> {
98
		let mut xcm_msg_hash = H256(sp_io::hashing::blake2_256(message));
99
		XCM_MESSAGE_HASH::using(&mut xcm_msg_hash, || {
100
			Inner::process_message(message, origin, meter, id)
101
		})
102
	}
103
}
104

            
105
pub use self::pallet::*;
106

            
107
382
#[frame_support::pallet(dev_mode)]
108
pub mod pallet {
109
	use super::*;
110
	use frame_support::pallet_prelude::*;
111

            
112
	#[pallet::config]
113
	pub trait Config: frame_system::Config + pallet_evm::Config {
114
		/// The overarching event type.
115
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
116
		/// Invalid transaction error
117
		type InvalidEvmTransactionError: From<TransactionValidationError>;
118
		/// Handler for applying an already validated transaction
119
		type ValidatedTransaction: ValidatedTransaction;
120
		/// Origin for xcm transact
121
		type XcmEthereumOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = H160>;
122
		/// Maximum Weight reserved for xcm in a block
123
		type ReservedXcmpWeight: Get<Weight>;
124
		/// Ensure proxy
125
		type EnsureProxy: EnsureProxy<Self::AccountId>;
126
		/// The origin that is allowed to resume or suspend the XCM to Ethereum executions.
127
		type ControllerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
128
		/// An origin that can submit a create tx type
129
		type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
130
	}
131

            
132
79
	#[pallet::pallet]
133
	#[pallet::without_storage_info]
134
	pub struct Pallet<T>(PhantomData<T>);
135

            
136
	/// Global nonce used for building Ethereum transaction payload.
137
816
	#[pallet::storage]
138
	#[pallet::getter(fn nonce)]
139
	pub(crate) type Nonce<T: Config> = StorageValue<_, U256, ValueQuery>;
140

            
141
	/// Whether or not Ethereum-XCM is suspended from executing
142
348
	#[pallet::storage]
143
	#[pallet::getter(fn ethereum_xcm_suspended)]
144
	pub(super) type EthereumXcmSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
145

            
146
	#[pallet::origin]
147
	pub type Origin = RawOrigin;
148

            
149
4
	#[pallet::error]
150
	pub enum Error<T> {
151
		/// Xcm to Ethereum execution is suspended
152
		EthereumXcmExecutionSuspended,
153
	}
154

            
155
	#[pallet::event]
156
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
157
	pub enum Event<T> {
158
		/// Ethereum transaction executed from XCM
159
		ExecutedFromXcm {
160
			xcm_msg_hash: H256,
161
			eth_tx_hash: H256,
162
		},
163
	}
164

            
165
244
	#[pallet::call]
166
	impl<T: Config> Pallet<T>
167
	where
168
		OriginFor<T>: Into<Result<RawOrigin, OriginFor<T>>>,
169
	{
170
		/// Xcm Transact an Ethereum transaction.
171
		/// Weight: Gas limit plus the db read involving the suspension check
172
		#[pallet::weight({
173
			let without_base_extrinsic_weight = false;
174
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
175
				match xcm_transaction {
176
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
177
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
178
				}
179
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
180
		})]
181
		pub fn transact(
182
			origin: OriginFor<T>,
183
			xcm_transaction: EthereumXcmTransaction,
184
109
		) -> DispatchResultWithPostInfo {
185
109
			let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
186
109
			ensure!(
187
109
				!EthereumXcmSuspended::<T>::get(),
188
1
				DispatchErrorWithPostInfo {
189
1
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
190
1
					post_info: PostDispatchInfo {
191
1
						actual_weight: Some(T::DbWeight::get().reads(1)),
192
1
						pays_fee: Pays::Yes
193
1
					}
194
1
				}
195
			);
196
108
			Self::validate_and_apply(source, xcm_transaction, false, None)
197
		}
198

            
199
		/// Xcm Transact an Ethereum transaction through proxy.
200
		/// Weight: Gas limit plus the db reads involving the suspension and proxy checks
201
		#[pallet::weight({
202
			let without_base_extrinsic_weight = false;
203
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
204
				match xcm_transaction {
205
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
206
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
207
				}
208
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(2))
209
		})]
210
		pub fn transact_through_proxy(
211
			origin: OriginFor<T>,
212
			transact_as: H160,
213
			xcm_transaction: EthereumXcmTransaction,
214
20
		) -> DispatchResultWithPostInfo {
215
20
			let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
216
20
			ensure!(
217
20
				!EthereumXcmSuspended::<T>::get(),
218
1
				DispatchErrorWithPostInfo {
219
1
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
220
1
					post_info: PostDispatchInfo {
221
1
						actual_weight: Some(T::DbWeight::get().reads(1)),
222
1
						pays_fee: Pays::Yes
223
1
					}
224
1
				}
225
			);
226
19
			let _ = T::EnsureProxy::ensure_ok(
227
19
				T::AddressMapping::into_account_id(transact_as),
228
19
				T::AddressMapping::into_account_id(source),
229
19
			)
230
19
			.map_err(|e| sp_runtime::DispatchErrorWithPostInfo {
231
11
				post_info: PostDispatchInfo {
232
11
					actual_weight: Some(T::DbWeight::get().reads(2)),
233
11
					pays_fee: Pays::Yes,
234
11
				},
235
11
				error: sp_runtime::DispatchError::Other(e),
236
19
			})?;
237

            
238
8
			Self::validate_and_apply(transact_as, xcm_transaction, false, None)
239
		}
240

            
241
		/// Suspends all Ethereum executions from XCM.
242
		///
243
		/// - `origin`: Must pass `ControllerOrigin`.
244
		#[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
245
3
		pub fn suspend_ethereum_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
246
3
			T::ControllerOrigin::ensure_origin(origin)?;
247

            
248
3
			EthereumXcmSuspended::<T>::put(true);
249
3

            
250
3
			Ok(())
251
		}
252

            
253
		/// Resumes all Ethereum executions from XCM.
254
		///
255
		/// - `origin`: Must pass `ControllerOrigin`.
256
		#[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
257
2
		pub fn resume_ethereum_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
258
2
			T::ControllerOrigin::ensure_origin(origin)?;
259

            
260
2
			EthereumXcmSuspended::<T>::put(false);
261
2

            
262
2
			Ok(())
263
		}
264

            
265
		/// Xcm Transact an Ethereum transaction, but allow to force the caller and create address.
266
		/// This call should be restricted (callable only by the runtime or governance).
267
		/// Weight: Gas limit plus the db reads involving the suspension and proxy checks
268
		#[pallet::weight({
269
			let without_base_extrinsic_weight = false;
270
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
271
				match xcm_transaction {
272
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
273
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
274
				}
275
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
276
		})]
277
		pub fn force_transact_as(
278
			origin: OriginFor<T>,
279
			transact_as: H160,
280
			xcm_transaction: EthereumXcmTransaction,
281
			force_create_address: Option<H160>,
282
40
		) -> DispatchResultWithPostInfo {
283
40
			T::ForceOrigin::ensure_origin(origin)?;
284
40
			ensure!(
285
40
				!EthereumXcmSuspended::<T>::get(),
286
				DispatchErrorWithPostInfo {
287
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
288
					post_info: PostDispatchInfo {
289
						actual_weight: Some(T::DbWeight::get().reads(1)),
290
						pays_fee: Pays::Yes
291
					}
292
				}
293
			);
294

            
295
40
			Self::validate_and_apply(transact_as, xcm_transaction, true, force_create_address)
296
		}
297
	}
298
}
299

            
300
impl<T: Config> Pallet<T> {
301
104
	fn transaction_len(transaction: &Transaction) -> u64 {
302
104
		transaction
303
104
			.encode()
304
104
			.len()
305
104
			// pallet + call indexes
306
104
			.saturating_add(2) as u64
307
104
	}
308

            
309
156
	fn validate_and_apply(
310
156
		source: H160,
311
156
		xcm_transaction: EthereumXcmTransaction,
312
156
		allow_create: bool,
313
156
		maybe_force_create_address: Option<H160>,
314
156
	) -> DispatchResultWithPostInfo {
315
156
		// The lack of a real signature where different callers with the
316
156
		// same nonce are providing identical transaction payloads results in a collision and
317
156
		// the same ethereum tx hash.
318
156
		// We use a global nonce instead the user nonce for all Xcm->Ethereum transactions to avoid
319
156
		// this.
320
156
		let current_nonce = Self::nonce();
321
156
		let error_weight = T::DbWeight::get().reads(1);
322
156

            
323
156
		let transaction: Option<Transaction> =
324
156
			xcm_transaction.into_transaction_v2(current_nonce, T::ChainId::get(), allow_create);
325
156
		if let Some(transaction) = transaction {
326
152
			let tx_hash = transaction.hash();
327
152
			let transaction_data: TransactionData = (&transaction).into();
328

            
329
152
			let (weight_limit, proof_size_base_cost) =
330
152
				match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
331
152
					transaction_data.gas_limit.unique_saturated_into(),
332
152
					true,
333
152
				) {
334
152
					weight_limit if weight_limit.proof_size() > 0 => (
335
104
						Some(weight_limit),
336
104
						Some(Self::transaction_len(&transaction)),
337
104
					),
338
48
					_ => (None, None),
339
				};
340

            
341
152
			let _ = CheckEvmTransaction::<T::InvalidEvmTransactionError>::new(
342
152
				CheckEvmTransactionConfig {
343
152
					evm_config: T::config(),
344
152
					block_gas_limit: U256::from(
345
152
						<T as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
346
152
							T::ReservedXcmpWeight::get(),
347
152
						),
348
152
					),
349
152
					base_fee: U256::zero(),
350
152
					chain_id: 0u64,
351
152
					is_transactional: true,
352
152
				},
353
152
				transaction_data.into(),
354
152
				weight_limit,
355
152
				proof_size_base_cost,
356
152
			)
357
152
			// We only validate the gas limit against the evm transaction cost.
358
152
			// No need to validate fee payment, as it is handled by the xcm executor.
359
152
			.validate_common()
360
152
			.map_err(|_| sp_runtime::DispatchErrorWithPostInfo {
361
8
				post_info: PostDispatchInfo {
362
8
					actual_weight: Some(error_weight),
363
8
					pays_fee: Pays::Yes,
364
8
				},
365
8
				error: sp_runtime::DispatchError::Other("Failed to validate ethereum transaction"),
366
152
			})?;
367

            
368
			// Once we know a new transaction hash exists - the user can afford storing the
369
			// transaction on chain - we increase the global nonce.
370
144
			<Nonce<T>>::put(current_nonce.saturating_add(U256::one()));
371

            
372
144
			let (dispatch_info, _) =
373
144
				T::ValidatedTransaction::apply(source, transaction, maybe_force_create_address)?;
374

            
375
144
			XCM_MESSAGE_HASH::with(|xcm_msg_hash| {
376
				Self::deposit_event(Event::ExecutedFromXcm {
377
					xcm_msg_hash: *xcm_msg_hash,
378
					eth_tx_hash: tx_hash,
379
				});
380
144
			});
381
144

            
382
144
			Ok(dispatch_info)
383
		} else {
384
4
			Err(sp_runtime::DispatchErrorWithPostInfo {
385
4
				post_info: PostDispatchInfo {
386
4
					actual_weight: Some(error_weight),
387
4
					pays_fee: Pays::Yes,
388
4
				},
389
4
				error: sp_runtime::DispatchError::Other("Cannot convert xcm payload to known type"),
390
4
			})
391
		}
392
156
	}
393
}