1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Copyright 2019-2022 PureStake Inc.
// This file is part of Moonbeam.

// Moonbeam is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Moonbeam is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.

//! # Ethereum Xcm pallet
//!
//! The Xcm Ethereum pallet is a bridge for Xcm Transact to Ethereum pallet

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

#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;

use ethereum_types::{H160, H256, U256};
use fp_ethereum::{TransactionData, ValidatedTransaction};
use fp_evm::{CheckEvmTransaction, CheckEvmTransactionConfig, TransactionValidationError};
use frame_support::{
	dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
	traits::{EnsureOrigin, Get, ProcessMessage},
	weights::Weight,
};
use frame_system::pallet_prelude::OriginFor;
use pallet_evm::{AddressMapping, GasWeightMapping};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{traits::UniqueSaturatedInto, DispatchErrorWithPostInfo, RuntimeDebug};
use sp_std::{marker::PhantomData, prelude::*};

pub use ethereum::{
	AccessListItem, BlockV2 as Block, LegacyTransactionMessage, Log, ReceiptV3 as Receipt,
	TransactionAction, TransactionV2 as Transaction,
};
pub use fp_rpc::TransactionStatus;
pub use xcm_primitives::{EnsureProxy, EthereumXcmTransaction, XcmToEthereum};

#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum RawOrigin {
	XcmEthereumTransaction(H160),
}

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

pub struct EnsureXcmEthereumTransaction;
impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O>
	for EnsureXcmEthereumTransaction
{
	type Success = H160;
	fn try_origin(o: O) -> Result<Self::Success, O> {
		o.into().map(|o| match o {
			RawOrigin::XcmEthereumTransaction(id) => id,
		})
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn try_successful_origin() -> Result<O, ()> {
		Ok(O::from(RawOrigin::XcmEthereumTransaction(
			Default::default(),
		)))
	}
}

environmental::environmental!(XCM_MESSAGE_HASH: H256);

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

	fn process_message(
		message: &[u8],
		origin: Self::Origin,
		meter: &mut frame_support::weights::WeightMeter,
		id: &mut [u8; 32],
	) -> Result<bool, frame_support::traits::ProcessMessageError> {
		let mut xcm_msg_hash = H256(sp_io::hashing::blake2_256(message));
		XCM_MESSAGE_HASH::using(&mut xcm_msg_hash, || {
			Inner::process_message(message, origin, meter, id)
		})
	}
}

pub use self::pallet::*;

#[frame_support::pallet(dev_mode)]
pub mod pallet {
	use super::*;
	use frame_support::pallet_prelude::*;

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

	#[pallet::pallet]
	#[pallet::without_storage_info]
	pub struct Pallet<T>(PhantomData<T>);

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

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

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

	#[pallet::error]
	pub enum Error<T> {
		/// Xcm to Ethereum execution is suspended
		EthereumXcmExecutionSuspended,
	}

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T> {
		/// Ethereum transaction executed from XCM
		ExecutedFromXcm {
			xcm_msg_hash: H256,
			eth_tx_hash: H256,
		},
	}

	#[pallet::call]
	impl<T: Config> Pallet<T>
	where
		OriginFor<T>: Into<Result<RawOrigin, OriginFor<T>>>,
	{
		/// Xcm Transact an Ethereum transaction.
		/// Weight: Gas limit plus the db read involving the suspension check
		#[pallet::weight({
			let without_base_extrinsic_weight = false;
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
				match xcm_transaction {
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
				}
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
		})]
		pub fn transact(
			origin: OriginFor<T>,
			xcm_transaction: EthereumXcmTransaction,
		) -> DispatchResultWithPostInfo {
			let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
			ensure!(
				!EthereumXcmSuspended::<T>::get(),
				DispatchErrorWithPostInfo {
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
					post_info: PostDispatchInfo {
						actual_weight: Some(T::DbWeight::get().reads(1)),
						pays_fee: Pays::Yes
					}
				}
			);
			Self::validate_and_apply(source, xcm_transaction, false, None)
		}

		/// Xcm Transact an Ethereum transaction through proxy.
		/// Weight: Gas limit plus the db reads involving the suspension and proxy checks
		#[pallet::weight({
			let without_base_extrinsic_weight = false;
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
				match xcm_transaction {
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
				}
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(2))
		})]
		pub fn transact_through_proxy(
			origin: OriginFor<T>,
			transact_as: H160,
			xcm_transaction: EthereumXcmTransaction,
		) -> DispatchResultWithPostInfo {
			let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
			ensure!(
				!EthereumXcmSuspended::<T>::get(),
				DispatchErrorWithPostInfo {
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
					post_info: PostDispatchInfo {
						actual_weight: Some(T::DbWeight::get().reads(1)),
						pays_fee: Pays::Yes
					}
				}
			);
			let _ = T::EnsureProxy::ensure_ok(
				T::AddressMapping::into_account_id(transact_as),
				T::AddressMapping::into_account_id(source),
			)
			.map_err(|e| sp_runtime::DispatchErrorWithPostInfo {
				post_info: PostDispatchInfo {
					actual_weight: Some(T::DbWeight::get().reads(2)),
					pays_fee: Pays::Yes,
				},
				error: sp_runtime::DispatchError::Other(e),
			})?;

			Self::validate_and_apply(transact_as, xcm_transaction, false, None)
		}

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

			EthereumXcmSuspended::<T>::put(true);

			Ok(())
		}

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

			EthereumXcmSuspended::<T>::put(false);

			Ok(())
		}

		/// Xcm Transact an Ethereum transaction, but allow to force the caller and create address.
		/// This call should be restricted (callable only by the runtime or governance).
		/// Weight: Gas limit plus the db reads involving the suspension and proxy checks
		#[pallet::weight({
			let without_base_extrinsic_weight = false;
			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
				match xcm_transaction {
					EthereumXcmTransaction::V1(v1_tx) =>  v1_tx.gas_limit.unique_saturated_into(),
					EthereumXcmTransaction::V2(v2_tx) =>  v2_tx.gas_limit.unique_saturated_into()
				}
			}, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
		})]
		pub fn force_transact_as(
			origin: OriginFor<T>,
			transact_as: H160,
			xcm_transaction: EthereumXcmTransaction,
			force_create_address: Option<H160>,
		) -> DispatchResultWithPostInfo {
			T::ForceOrigin::ensure_origin(origin)?;
			ensure!(
				!EthereumXcmSuspended::<T>::get(),
				DispatchErrorWithPostInfo {
					error: Error::<T>::EthereumXcmExecutionSuspended.into(),
					post_info: PostDispatchInfo {
						actual_weight: Some(T::DbWeight::get().reads(1)),
						pays_fee: Pays::Yes
					}
				}
			);

			Self::validate_and_apply(transact_as, xcm_transaction, true, force_create_address)
		}
	}
}

impl<T: Config> Pallet<T> {
	fn transaction_len(transaction: &Transaction) -> u64 {
		transaction
			.encode()
			.len()
			// pallet + call indexes
			.saturating_add(2) as u64
	}

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

		let transaction: Option<Transaction> =
			xcm_transaction.into_transaction_v2(current_nonce, T::ChainId::get(), allow_create);
		if let Some(transaction) = transaction {
			let tx_hash = transaction.hash();
			let transaction_data: TransactionData = (&transaction).into();

			let (weight_limit, proof_size_base_cost) =
				match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
					transaction_data.gas_limit.unique_saturated_into(),
					true,
				) {
					weight_limit if weight_limit.proof_size() > 0 => (
						Some(weight_limit),
						Some(Self::transaction_len(&transaction)),
					),
					_ => (None, None),
				};

			let _ = CheckEvmTransaction::<T::InvalidEvmTransactionError>::new(
				CheckEvmTransactionConfig {
					evm_config: T::config(),
					block_gas_limit: U256::from(
						<T as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
							T::ReservedXcmpWeight::get(),
						),
					),
					base_fee: U256::zero(),
					chain_id: 0u64,
					is_transactional: true,
				},
				transaction_data.into(),
				weight_limit,
				proof_size_base_cost,
			)
			// We only validate the gas limit against the evm transaction cost.
			// No need to validate fee payment, as it is handled by the xcm executor.
			.validate_common()
			.map_err(|_| sp_runtime::DispatchErrorWithPostInfo {
				post_info: PostDispatchInfo {
					actual_weight: Some(error_weight),
					pays_fee: Pays::Yes,
				},
				error: sp_runtime::DispatchError::Other("Failed to validate ethereum transaction"),
			})?;

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

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

			XCM_MESSAGE_HASH::with(|xcm_msg_hash| {
				Self::deposit_event(Event::ExecutedFromXcm {
					xcm_msg_hash: *xcm_msg_hash,
					eth_tx_hash: tx_hash,
				});
			});

			Ok(dispatch_info)
		} else {
			Err(sp_runtime::DispatchErrorWithPostInfo {
				post_info: PostDispatchInfo {
					actual_weight: Some(error_weight),
					pays_fee: Pays::Yes,
				},
				error: sp_runtime::DispatchError::Other("Cannot convert xcm payload to known type"),
			})
		}
	}
}