1#![cfg_attr(not(feature = "std"), no_std)]
23#![allow(clippy::comparison_chain, clippy::large_enum_variant)]
24
25#[cfg(test)]
26mod mock;
27#[cfg(test)]
28mod tests;
29
30use ethereum_types::{H160, H256, U256};
31use fp_ethereum::{TransactionData, ValidatedTransaction};
32use fp_evm::{CheckEvmTransaction, CheckEvmTransactionConfig, TransactionValidationError};
33use frame_support::{
34 dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
35 traits::{EnsureOrigin, Get, ProcessMessage},
36 weights::Weight,
37};
38use frame_system::pallet_prelude::OriginFor;
39use pallet_evm::{AddressMapping, GasWeightMapping};
40use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
41use scale_info::TypeInfo;
42use sp_runtime::{traits::UniqueSaturatedInto, DispatchErrorWithPostInfo, RuntimeDebug};
43use sp_std::{marker::PhantomData, prelude::*};
44
45pub use ethereum::{
46 AccessListItem, BlockV3 as Block, LegacyTransactionMessage, Log, ReceiptV4 as Receipt,
47 TransactionAction, TransactionV3 as Transaction,
48};
49pub use fp_rpc::TransactionStatus;
50pub use xcm_primitives::{EnsureProxy, EthereumXcmTransaction, XcmToEthereum};
51
52#[derive(
53 PartialEq,
54 Eq,
55 Clone,
56 Encode,
57 Decode,
58 RuntimeDebug,
59 TypeInfo,
60 MaxEncodedLen,
61 DecodeWithMemTracking,
62)]
63pub enum RawOrigin {
64 XcmEthereumTransaction(H160),
65}
66
67pub fn ensure_xcm_ethereum_transaction<OuterOrigin>(o: OuterOrigin) -> Result<H160, &'static str>
68where
69 OuterOrigin: Into<Result<RawOrigin, OuterOrigin>>,
70{
71 match o.into() {
72 Ok(RawOrigin::XcmEthereumTransaction(n)) => Ok(n),
73 _ => Err("bad origin: expected to be a xcm Ethereum transaction"),
74 }
75}
76
77pub struct EnsureXcmEthereumTransaction;
78impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O>
79 for EnsureXcmEthereumTransaction
80{
81 type Success = H160;
82 fn try_origin(o: O) -> Result<Self::Success, O> {
83 o.into().map(|o| match o {
84 RawOrigin::XcmEthereumTransaction(id) => id,
85 })
86 }
87
88 #[cfg(feature = "runtime-benchmarks")]
89 fn try_successful_origin() -> Result<O, ()> {
90 Ok(O::from(RawOrigin::XcmEthereumTransaction(
91 Default::default(),
92 )))
93 }
94}
95
96environmental::environmental!(XCM_MESSAGE_HASH: H256);
97
98pub struct MessageProcessorWrapper<Inner>(core::marker::PhantomData<Inner>);
99impl<Inner: ProcessMessage> ProcessMessage for MessageProcessorWrapper<Inner> {
100 type Origin = <Inner as ProcessMessage>::Origin;
101
102 fn process_message(
103 message: &[u8],
104 origin: Self::Origin,
105 meter: &mut frame_support::weights::WeightMeter,
106 id: &mut [u8; 32],
107 ) -> Result<bool, frame_support::traits::ProcessMessageError> {
108 let mut xcm_msg_hash = H256(sp_io::hashing::blake2_256(message));
109 XCM_MESSAGE_HASH::using(&mut xcm_msg_hash, || {
110 Inner::process_message(message, origin, meter, id)
111 })
112 }
113}
114
115pub use self::pallet::*;
116
117#[frame_support::pallet(dev_mode)]
118pub mod pallet {
119 use super::*;
120 use fp_evm::AccountProvider;
121 use frame_support::pallet_prelude::*;
122
123 #[pallet::config]
124 pub trait Config:
125 frame_system::Config<RuntimeEvent: From<Event<Self>>> + pallet_evm::Config
126 {
127 type InvalidEvmTransactionError: From<TransactionValidationError>;
129 type ValidatedTransaction: ValidatedTransaction;
131 type XcmEthereumOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = H160>;
133 type ReservedXcmpWeight: Get<Weight>;
135 type EnsureProxy: EnsureProxy<
137 <<Self as pallet_evm::Config>::AccountProvider as AccountProvider>::AccountId,
138 >;
139 type ControllerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
141 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
143 }
144
145 #[pallet::pallet]
146 #[pallet::without_storage_info]
147 pub struct Pallet<T>(PhantomData<T>);
148
149 #[pallet::storage]
151 #[pallet::getter(fn nonce)]
152 pub(crate) type Nonce<T: Config> = StorageValue<_, U256, ValueQuery>;
153
154 #[pallet::storage]
156 #[pallet::getter(fn ethereum_xcm_suspended)]
157 pub(super) type EthereumXcmSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
158
159 #[pallet::origin]
160 pub type Origin = RawOrigin;
161
162 #[pallet::error]
163 pub enum Error<T> {
164 EthereumXcmExecutionSuspended,
166 }
167
168 #[pallet::event]
169 #[pallet::generate_deposit(pub(super) fn deposit_event)]
170 pub enum Event<T> {
171 ExecutedFromXcm {
173 xcm_msg_hash: H256,
174 eth_tx_hash: H256,
175 },
176 }
177
178 #[pallet::call]
179 impl<T: Config> Pallet<T>
180 where
181 OriginFor<T>: Into<Result<RawOrigin, OriginFor<T>>>,
182 {
183 #[pallet::weight({
186 let without_base_extrinsic_weight = false;
187 <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
188 match xcm_transaction {
189 EthereumXcmTransaction::V1(v1_tx) => v1_tx.gas_limit.unique_saturated_into(),
190 EthereumXcmTransaction::V2(v2_tx) => v2_tx.gas_limit.unique_saturated_into(),
191 EthereumXcmTransaction::V3(v3_tx) => v3_tx.gas_limit.unique_saturated_into(),
192 }
193 }, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
194 })]
195 pub fn transact(
196 origin: OriginFor<T>,
197 xcm_transaction: EthereumXcmTransaction,
198 ) -> DispatchResultWithPostInfo {
199 let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
200 ensure!(
201 !EthereumXcmSuspended::<T>::get(),
202 DispatchErrorWithPostInfo {
203 error: Error::<T>::EthereumXcmExecutionSuspended.into(),
204 post_info: PostDispatchInfo {
205 actual_weight: Some(T::DbWeight::get().reads(1)),
206 pays_fee: Pays::Yes
207 }
208 }
209 );
210 Self::validate_and_apply(source, xcm_transaction, false, None)
211 }
212
213 #[pallet::weight({
216 let without_base_extrinsic_weight = false;
217 <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
218 match xcm_transaction {
219 EthereumXcmTransaction::V1(v1_tx) => v1_tx.gas_limit.unique_saturated_into(),
220 EthereumXcmTransaction::V2(v2_tx) => v2_tx.gas_limit.unique_saturated_into(),
221 EthereumXcmTransaction::V3(v3_tx) => v3_tx.gas_limit.unique_saturated_into(),
222 }
223 }, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(2))
224 })]
225 pub fn transact_through_proxy(
226 origin: OriginFor<T>,
227 transact_as: H160,
228 xcm_transaction: EthereumXcmTransaction,
229 ) -> DispatchResultWithPostInfo {
230 let source = T::XcmEthereumOrigin::ensure_origin(origin)?;
231 ensure!(
232 !EthereumXcmSuspended::<T>::get(),
233 DispatchErrorWithPostInfo {
234 error: Error::<T>::EthereumXcmExecutionSuspended.into(),
235 post_info: PostDispatchInfo {
236 actual_weight: Some(T::DbWeight::get().reads(1)),
237 pays_fee: Pays::Yes
238 }
239 }
240 );
241 let _ = T::EnsureProxy::ensure_ok(
242 T::AddressMapping::into_account_id(transact_as),
243 T::AddressMapping::into_account_id(source),
244 )
245 .map_err(|e| sp_runtime::DispatchErrorWithPostInfo {
246 post_info: PostDispatchInfo {
247 actual_weight: Some(T::DbWeight::get().reads(2)),
248 pays_fee: Pays::Yes,
249 },
250 error: sp_runtime::DispatchError::Other(e),
251 })?;
252
253 Self::validate_and_apply(transact_as, xcm_transaction, false, None)
254 }
255
256 #[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
260 pub fn suspend_ethereum_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
261 T::ControllerOrigin::ensure_origin(origin)?;
262
263 EthereumXcmSuspended::<T>::put(true);
264
265 Ok(())
266 }
267
268 #[pallet::weight((T::DbWeight::get().writes(1), DispatchClass::Operational,))]
272 pub fn resume_ethereum_xcm_execution(origin: OriginFor<T>) -> DispatchResult {
273 T::ControllerOrigin::ensure_origin(origin)?;
274
275 EthereumXcmSuspended::<T>::put(false);
276
277 Ok(())
278 }
279
280 #[pallet::weight({
284 let without_base_extrinsic_weight = false;
285 <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
286 match xcm_transaction {
287 EthereumXcmTransaction::V1(v1_tx) => v1_tx.gas_limit.unique_saturated_into(),
288 EthereumXcmTransaction::V2(v2_tx) => v2_tx.gas_limit.unique_saturated_into(),
289 EthereumXcmTransaction::V3(v3_tx) => v3_tx.gas_limit.unique_saturated_into(),
290 }
291 }, without_base_extrinsic_weight).saturating_add(T::DbWeight::get().reads(1))
292 })]
293 pub fn force_transact_as(
294 origin: OriginFor<T>,
295 transact_as: H160,
296 xcm_transaction: EthereumXcmTransaction,
297 force_create_address: Option<H160>,
298 ) -> DispatchResultWithPostInfo {
299 T::ForceOrigin::ensure_origin(origin)?;
300 ensure!(
301 !EthereumXcmSuspended::<T>::get(),
302 DispatchErrorWithPostInfo {
303 error: Error::<T>::EthereumXcmExecutionSuspended.into(),
304 post_info: PostDispatchInfo {
305 actual_weight: Some(T::DbWeight::get().reads(1)),
306 pays_fee: Pays::Yes
307 }
308 }
309 );
310
311 Self::validate_and_apply(transact_as, xcm_transaction, true, force_create_address)
312 }
313 }
314}
315
316impl<T: Config> Pallet<T> {
317 fn transaction_len(transaction: &Transaction) -> u64 {
318 transaction
319 .encode()
320 .len()
321 .saturating_add(2) as u64
323 }
324
325 fn validate_and_apply(
326 source: H160,
327 xcm_transaction: EthereumXcmTransaction,
328 allow_create: bool,
329 maybe_force_create_address: Option<H160>,
330 ) -> DispatchResultWithPostInfo {
331 let current_nonce = Self::nonce();
337 let error_weight = T::DbWeight::get().reads(1);
338
339 let transaction: Option<Transaction> =
340 xcm_transaction.into_transaction(current_nonce, T::ChainId::get(), allow_create);
341 if let Some(transaction) = transaction {
342 let tx_hash = transaction.hash();
343 let transaction_data: TransactionData = (&transaction).into();
344
345 let (weight_limit, proof_size_base_cost) =
346 match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
347 transaction_data.gas_limit.unique_saturated_into(),
348 true,
349 ) {
350 weight_limit if weight_limit.proof_size() > 0 => (
351 Some(weight_limit),
352 Some(Self::transaction_len(&transaction)),
353 ),
354 _ => (None, None),
355 };
356
357 let _ = CheckEvmTransaction::<T::InvalidEvmTransactionError>::new(
358 CheckEvmTransactionConfig {
359 evm_config: T::config(),
360 block_gas_limit: U256::from(
361 <T as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
362 T::ReservedXcmpWeight::get(),
363 ),
364 ),
365 base_fee: U256::zero(),
366 chain_id: 0u64,
367 is_transactional: true,
368 },
369 transaction_data.into(),
370 weight_limit,
371 proof_size_base_cost,
372 )
373 .validate_common()
376 .map_err(|_| sp_runtime::DispatchErrorWithPostInfo {
377 post_info: PostDispatchInfo {
378 actual_weight: Some(error_weight),
379 pays_fee: Pays::Yes,
380 },
381 error: sp_runtime::DispatchError::Other("Failed to validate ethereum transaction"),
382 })?;
383
384 <Nonce<T>>::put(current_nonce.saturating_add(U256::one()));
387
388 let (dispatch_info, _) =
389 T::ValidatedTransaction::apply(source, transaction, maybe_force_create_address)?;
390
391 XCM_MESSAGE_HASH::with(|xcm_msg_hash| {
392 Self::deposit_event(Event::ExecutedFromXcm {
393 xcm_msg_hash: *xcm_msg_hash,
394 eth_tx_hash: tx_hash,
395 });
396 });
397
398 Ok(dispatch_info)
399 } else {
400 Err(sp_runtime::DispatchErrorWithPostInfo {
401 post_info: PostDispatchInfo {
402 actual_weight: Some(error_weight),
403 pays_fee: Pays::Yes,
404 },
405 error: sp_runtime::DispatchError::Other("Cannot convert xcm payload to known type"),
406 })
407 }
408 }
409}