1#![cfg_attr(not(feature = "std"), no_std)]
41
42use parity_scale_codec::{Decode, Encode};
43use scale_info::TypeInfo;
44
45use ethereum_types::U256;
46use fp_ethereum::{Transaction, TransactionData, ValidatedTransaction};
47use fp_evm::{
48 CallInfo, CallOrCreateInfo, CheckEvmTransaction, CheckEvmTransactionConfig, ExitReason,
49 ExitSucceed, TransactionValidationError,
50};
51use pallet_evm::GasWeightMapping;
52
53use frame_support::{
54 dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
55 pallet_prelude::*,
56};
57use frame_system::pallet_prelude::*;
58#[cfg(feature = "runtime-benchmarks")]
59use sp_runtime::traits::TrailingZeroInput;
60use sp_runtime::traits::UniqueSaturatedInto;
61use sp_std::{marker::PhantomData, result::Result};
62
63use astar_primitives::{
64 ethereum_checked::CheckedEthereumTx,
65 evm::{UnifiedAddressMapper, H160},
66};
67
68pub use pallet::*;
69
70#[cfg(feature = "runtime-benchmarks")]
71mod benchmarking;
72
73pub mod weights;
76pub use weights::WeightInfo;
77
78mod mock;
79mod tests;
80
81pub type WeightInfoOf<T> = <T as Config>::WeightInfo;
82
83#[derive(
85 PartialEq,
86 Eq,
87 Clone,
88 Encode,
89 Decode,
90 DecodeWithMemTracking,
91 RuntimeDebug,
92 TypeInfo,
93 MaxEncodedLen,
94)]
95pub enum RawOrigin<AccountId> {
96 XcmEthereumTx(AccountId),
97}
98
99pub struct EnsureXcmEthereumTx<AccountId>(PhantomData<AccountId>);
101impl<O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, AccountId: Decode>
102 EnsureOrigin<O> for EnsureXcmEthereumTx<AccountId>
103{
104 type Success = AccountId;
105
106 fn try_origin(o: O) -> Result<Self::Success, O> {
107 o.into().map(|o| match o {
108 RawOrigin::XcmEthereumTx(account_id) => account_id,
109 })
110 }
111
112 #[cfg(feature = "runtime-benchmarks")]
113 fn try_successful_origin() -> Result<O, ()> {
114 let zero_account_id =
115 AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?;
116 Ok(O::from(RawOrigin::XcmEthereumTx(zero_account_id)))
117 }
118}
119
120#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
122pub enum CheckedEthereumTxKind {
123 Xcm,
125}
126
127#[frame_support::pallet]
128pub mod pallet {
129 use super::*;
130
131 #[pallet::pallet]
132 pub struct Pallet<T>(PhantomData<T>);
133
134 #[pallet::config]
135 pub trait Config: frame_system::Config + pallet_evm::Config {
136 type ReservedXcmpWeight: Get<Weight>;
138
139 type InvalidEvmTransactionError: From<TransactionValidationError>;
141
142 type ValidatedTransaction: ValidatedTransaction;
144
145 type AddressMapper: UnifiedAddressMapper<Self::AccountId>;
147
148 type XcmTransactOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
150
151 type WeightInfo: WeightInfo;
153 }
154
155 #[pallet::origin]
156 pub type Origin<T> = RawOrigin<<T as frame_system::Config>::AccountId>;
157
158 #[pallet::storage]
161 pub type Nonce<T: Config> = StorageValue<_, U256, ValueQuery>;
162
163 #[pallet::call]
164 impl<T: Config> Pallet<T> {
165 #[pallet::call_index(0)]
168 #[pallet::weight({
169 let weight_limit = T::GasWeightMapping::gas_to_weight(tx.gas_limit.unique_saturated_into(), false);
170 weight_limit.saturating_add(WeightInfoOf::<T>::transact_without_apply())
171 })]
172 pub fn transact(origin: OriginFor<T>, tx: CheckedEthereumTx) -> DispatchResultWithPostInfo {
173 let source = T::XcmTransactOrigin::ensure_origin(origin)?;
174 Self::do_transact(
175 T::AddressMapper::to_h160_or_default(&source).into_address(),
176 tx.into(),
177 CheckedEthereumTxKind::Xcm,
178 false,
179 )
180 .map(|(post_info, _)| post_info)
181 }
182 }
183}
184
185impl<T: Config> Pallet<T> {
186 fn do_transact(
188 source: H160,
189 checked_tx: CheckedEthereumTx,
190 tx_kind: CheckedEthereumTxKind,
191 skip_apply: bool,
192 ) -> Result<(PostDispatchInfo, CallInfo), DispatchErrorWithPostInfo> {
193 let chain_id = T::ChainId::get();
194 let nonce = Nonce::<T>::get();
195 let tx: Transaction = checked_tx
196 .into_ethereum_tx(Nonce::<T>::get(), chain_id)
197 .into();
198 let tx_data: TransactionData = (&tx).into();
199
200 let (weight_limit, proof_size_base_cost) =
201 match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
202 tx_data.gas_limit.unique_saturated_into(),
203 true,
204 ) {
205 weight_limit if weight_limit.proof_size() > 0 => (
206 Some(weight_limit),
207 Some(WeightInfoOf::<T>::transact_without_apply().proof_size()),
209 ),
210 _ => (None, None),
211 };
212
213 let _ = CheckEvmTransaction::<T::InvalidEvmTransactionError>::new(
215 CheckEvmTransactionConfig {
216 evm_config: T::config(),
217 block_gas_limit: U256::from(Self::block_gas_limit(&tx_kind)),
218 base_fee: U256::zero(),
219 chain_id,
220 is_transactional: true,
221 },
222 tx_data.into(),
223 weight_limit,
224 proof_size_base_cost,
225 )
226 .validate_common()
228 .map_err(|_| DispatchErrorWithPostInfo {
229 post_info: PostDispatchInfo {
230 actual_weight: Some(
232 WeightInfoOf::<T>::transact_without_apply()
233 .saturating_sub(T::DbWeight::get().writes(1)),
234 ),
235 pays_fee: Pays::Yes,
236 },
237 error: DispatchError::Other("Failed to validate Ethereum tx"),
238 })?;
239
240 Nonce::<T>::put(nonce.saturating_add(U256::one()));
241
242 if skip_apply {
243 return Ok((
244 PostDispatchInfo {
245 actual_weight: Some(WeightInfoOf::<T>::transact_without_apply()),
246 pays_fee: Pays::Yes,
247 },
248 CallInfo {
249 exit_reason: ExitReason::Succeed(ExitSucceed::Returned),
250 value: Default::default(),
251 used_gas: fp_evm::UsedGas {
252 standard: checked_tx.gas_limit,
253 effective: checked_tx.gas_limit,
254 },
255 weight_info: None,
256 logs: Default::default(),
257 },
258 ));
259 }
260
261 let (post_info, apply_info) = T::ValidatedTransaction::apply(source, tx, None)?;
263 match apply_info {
264 CallOrCreateInfo::Call(info) => Ok((post_info, info)),
265 CallOrCreateInfo::Create(_) => {
267 unreachable!("Cannot create a 'Create' transaction; qed")
268 }
269 }
270 }
271
272 fn block_gas_limit(tx_kind: &CheckedEthereumTxKind) -> u64 {
274 let weight_limit = match tx_kind {
275 CheckedEthereumTxKind::Xcm => T::ReservedXcmpWeight::get(),
276 };
277 T::GasWeightMapping::weight_to_gas(weight_limit)
278 }
279
280 #[cfg(feature = "runtime-benchmarks")]
283 pub fn transact_without_apply(
284 origin: OriginFor<T>,
285 tx: CheckedEthereumTx,
286 ) -> DispatchResultWithPostInfo {
287 let source = T::XcmTransactOrigin::ensure_origin(origin)?;
288 Self::do_transact(
289 T::AddressMapper::to_h160_or_default(&source).into_address(),
290 tx.into(),
291 CheckedEthereumTxKind::Xcm,
292 true,
293 )
294 .map(|(post_info, _)| post_info)
295 }
296}