#![cfg_attr(not(feature = "std"), no_std)]
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use ethereum_types::{H160, U256};
use fp_ethereum::{TransactionData, ValidatedTransaction};
use fp_evm::{
CallInfo, CallOrCreateInfo, CheckEvmTransaction, CheckEvmTransactionConfig, ExitReason,
ExitSucceed, TransactionValidationError,
};
use pallet_evm::GasWeightMapping;
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
pallet_prelude::*,
};
use frame_system::pallet_prelude::*;
#[cfg(feature = "runtime-benchmarks")]
use sp_runtime::traits::TrailingZeroInput;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_std::{marker::PhantomData, result::Result};
use astar_primitives::{ethereum_checked::CheckedEthereumTx, evm::UnifiedAddressMapper};
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
mod mock;
mod tests;
pub type WeightInfoOf<T> = <T as Config>::WeightInfo;
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum RawOrigin<AccountId> {
XcmEthereumTx(AccountId),
}
pub struct EnsureXcmEthereumTx<AccountId>(PhantomData<AccountId>);
impl<O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>, AccountId: Decode>
EnsureOrigin<O> for EnsureXcmEthereumTx<AccountId>
{
type Success = AccountId;
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().map(|o| match o {
RawOrigin::XcmEthereumTx(account_id) => account_id,
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
let zero_account_id =
AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?;
Ok(O::from(RawOrigin::XcmEthereumTx(zero_account_id)))
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum CheckedEthereumTxKind {
Xcm,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config: frame_system::Config + pallet_evm::Config {
type ReservedXcmpWeight: Get<Weight>;
type InvalidEvmTransactionError: From<TransactionValidationError>;
type ValidatedTransaction: ValidatedTransaction;
type AddressMapper: UnifiedAddressMapper<Self::AccountId>;
type XcmTransactOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
type WeightInfo: WeightInfo;
}
#[pallet::origin]
pub type Origin<T> = RawOrigin<<T as frame_system::Config>::AccountId>;
#[pallet::storage]
pub type Nonce<T: Config> = StorageValue<_, U256, ValueQuery>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight({
let weight_limit = T::GasWeightMapping::gas_to_weight(tx.gas_limit.unique_saturated_into(), false);
weight_limit.saturating_add(WeightInfoOf::<T>::transact_without_apply())
})]
pub fn transact(origin: OriginFor<T>, tx: CheckedEthereumTx) -> DispatchResultWithPostInfo {
let source = T::XcmTransactOrigin::ensure_origin(origin)?;
Self::do_transact(
T::AddressMapper::to_h160_or_default(&source).into_address(),
tx.into(),
CheckedEthereumTxKind::Xcm,
false,
)
.map(|(post_info, _)| post_info)
}
}
}
impl<T: Config> Pallet<T> {
fn do_transact(
source: H160,
checked_tx: CheckedEthereumTx,
tx_kind: CheckedEthereumTxKind,
skip_apply: bool,
) -> Result<(PostDispatchInfo, CallInfo), DispatchErrorWithPostInfo> {
let chain_id = T::ChainId::get();
let nonce = Nonce::<T>::get();
let tx = checked_tx.into_ethereum_tx(Nonce::<T>::get(), chain_id);
let tx_data: TransactionData = (&tx).into();
let (weight_limit, proof_size_base_cost) =
match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
tx_data.gas_limit.unique_saturated_into(),
true,
) {
weight_limit if weight_limit.proof_size() > 0 => (
Some(weight_limit),
Some(WeightInfoOf::<T>::transact_without_apply().proof_size()),
),
_ => (None, None),
};
let _ = CheckEvmTransaction::<T::InvalidEvmTransactionError>::new(
CheckEvmTransactionConfig {
evm_config: T::config(),
block_gas_limit: U256::from(Self::block_gas_limit(&tx_kind)),
base_fee: U256::zero(),
chain_id,
is_transactional: true,
},
tx_data.into(),
weight_limit,
proof_size_base_cost,
)
.validate_common()
.map_err(|_| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(
WeightInfoOf::<T>::transact_without_apply()
.saturating_sub(T::DbWeight::get().writes(1)),
),
pays_fee: Pays::Yes,
},
error: DispatchError::Other("Failed to validate Ethereum tx"),
})?;
Nonce::<T>::put(nonce.saturating_add(U256::one()));
if skip_apply {
return Ok((
PostDispatchInfo {
actual_weight: Some(WeightInfoOf::<T>::transact_without_apply()),
pays_fee: Pays::Yes,
},
CallInfo {
exit_reason: ExitReason::Succeed(ExitSucceed::Returned),
value: Default::default(),
used_gas: fp_evm::UsedGas {
standard: checked_tx.gas_limit,
effective: checked_tx.gas_limit,
},
weight_info: None,
logs: Default::default(),
},
));
}
let (post_info, apply_info) = T::ValidatedTransaction::apply(source, tx)?;
match apply_info {
CallOrCreateInfo::Call(info) => Ok((post_info, info)),
CallOrCreateInfo::Create(_) => {
unreachable!("Cannot create a 'Create' transaction; qed")
}
}
}
fn block_gas_limit(tx_kind: &CheckedEthereumTxKind) -> u64 {
let weight_limit = match tx_kind {
CheckedEthereumTxKind::Xcm => T::ReservedXcmpWeight::get(),
};
T::GasWeightMapping::weight_to_gas(weight_limit)
}
#[cfg(feature = "runtime-benchmarks")]
pub fn transact_without_apply(
origin: OriginFor<T>,
tx: CheckedEthereumTx,
) -> DispatchResultWithPostInfo {
let source = T::XcmTransactOrigin::ensure_origin(origin)?;
Self::do_transact(
T::AddressMapper::to_h160_or_default(&source).into_address(),
tx.into(),
CheckedEthereumTxKind::Xcm,
true,
)
.map(|(post_info, _)| post_info)
}
}