#![cfg_attr(not(feature = "std"), no_std)]
#![warn(unused_crate_dependencies)]
#![allow(clippy::too_many_arguments)]
extern crate alloc;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
pub mod runner;
#[cfg(test)]
mod tests;
pub mod weights;
use alloc::{borrow::Cow, collections::btree_map::BTreeMap, vec::Vec};
use core::cmp::min;
pub use evm::{
Config as EvmConfig, Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed,
};
use hash_db::Hasher;
use impl_trait_for_tuples::impl_for_tuples;
use scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use frame_support::{
dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
storage::KeyPrefixIterator,
traits::{
fungible::{Balanced, Credit, Debt},
tokens::{
currency::Currency,
fungible::Inspect,
imbalance::{Imbalance, OnUnbalanced, SignedImbalance},
ExistenceRequirement, Fortitude, Precision, Preservation, WithdrawReasons,
},
FindAuthor, Get, Time,
},
weights::Weight,
};
use frame_system::RawOrigin;
use sp_core::{H160, H256, U256};
use sp_runtime::{
traits::{BadOrigin, NumberFor, Saturating, UniqueSaturatedInto, Zero},
AccountId32, DispatchErrorWithPostInfo,
};
use fp_account::AccountId20;
use fp_evm::GenesisAccount;
pub use fp_evm::{
Account, AccountProvider, CallInfo, CreateInfo, ExecutionInfoV2 as ExecutionInfo,
FeeCalculator, IsPrecompileResult, LinearCostPrecompile, Log, Precompile, PrecompileFailure,
PrecompileHandle, PrecompileOutput, PrecompileResult, PrecompileSet,
TransactionValidationError, Vicinity,
};
pub use self::{
pallet::*,
runner::{Runner, RunnerError},
weights::WeightInfo,
};
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
#[pallet::no_default]
type AccountProvider: AccountProvider;
type FeeCalculator: FeeCalculator;
type GasWeightMapping: GasWeightMapping;
type WeightPerGas: Get<Weight>;
#[pallet::no_default]
type BlockHashMapping: BlockHashMapping;
#[pallet::no_default_bounds]
type CallOrigin: EnsureAddressOrigin<Self::RuntimeOrigin>;
#[pallet::no_default_bounds]
type CreateOriginFilter: EnsureCreateOrigin<Self>;
#[pallet::no_default_bounds]
type CreateInnerOriginFilter: EnsureCreateOrigin<Self>;
#[pallet::no_default_bounds]
type WithdrawOrigin: EnsureAddressOrigin<Self::RuntimeOrigin, Success = AccountIdOf<Self>>;
#[pallet::no_default_bounds]
type AddressMapping: AddressMapping<AccountIdOf<Self>>;
#[pallet::no_default]
type Currency: Currency<AccountIdOf<Self>> + Inspect<AccountIdOf<Self>>;
#[pallet::no_default_bounds]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type PrecompilesType: PrecompileSet;
type PrecompilesValue: Get<Self::PrecompilesType>;
type ChainId: Get<u64>;
type BlockGasLimit: Get<U256>;
#[pallet::no_default]
type Runner: Runner<Self>;
#[pallet::no_default_bounds]
type OnChargeTransaction: OnChargeEVMTransaction<Self>;
#[pallet::no_default_bounds]
type OnCreate: OnCreate<Self>;
type FindAuthor: FindAuthor<H160>;
type GasLimitPovSizeRatio: Get<u64>;
type GasLimitStorageGrowthRatio: Get<u64>;
#[pallet::no_default]
type Timestamp: Time;
type WeightInfo: WeightInfo;
fn config() -> &'static EvmConfig {
&CANCUN_CONFIG
}
}
pub mod config_preludes {
use super::*;
use core::str::FromStr;
use frame_support::{derive_impl, parameter_types, ConsensusEngineId};
use sp_runtime::traits::BlakeTwo256;
pub struct TestDefaultConfig;
#[derive_impl(
frame_system::config_preludes::SolochainDefaultConfig,
no_aggregated_types
)]
impl frame_system::DefaultConfig for TestDefaultConfig {}
const BLOCK_GAS_LIMIT: u64 = 150_000_000;
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
const MAX_STORAGE_GROWTH: u64 = 400 * 1024;
parameter_types! {
pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT);
pub const ChainId: u64 = 42;
pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE);
pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH);
pub WeightPerGas: Weight = Weight::from_parts(20_000, 0);
}
#[register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
type CallOrigin = EnsureAddressRoot<Self::AccountId>;
type WithdrawOrigin = EnsureAddressNever<Self::AccountId>;
type AddressMapping = HashedAddressMapping<BlakeTwo256>;
type FeeCalculator = FixedGasPrice;
type GasWeightMapping = FixedGasWeightMapping<Self>;
type WeightPerGas = WeightPerGas;
#[inject_runtime_type]
type RuntimeEvent = ();
type PrecompilesType = ();
type PrecompilesValue = ();
type ChainId = ChainId;
type BlockGasLimit = BlockGasLimit;
type OnChargeTransaction = ();
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type CreateOriginFilter = ();
type CreateInnerOriginFilter = ();
type WeightInfo = ();
}
impl FixedGasWeightMappingAssociatedTypes for TestDefaultConfig {
type WeightPerGas = <Self as DefaultConfig>::WeightPerGas;
type BlockWeights = <Self as frame_system::DefaultConfig>::BlockWeights;
type GasLimitPovSizeRatio = <Self as DefaultConfig>::GasLimitPovSizeRatio;
}
pub struct FixedGasPrice;
impl FeeCalculator for FixedGasPrice {
fn min_gas_price() -> (U256, Weight) {
(1.into(), Weight::zero())
}
}
pub struct FindAuthorTruncated;
impl FindAuthor<H160> for FindAuthorTruncated {
fn find_author<'a, I>(_digests: I) -> Option<H160>
where
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
{
Some(H160::from_str("1234500000000000000000000000000000000000").unwrap())
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::withdraw())]
pub fn withdraw(
origin: OriginFor<T>,
address: H160,
value: BalanceOf<T>,
) -> DispatchResult {
let destination = T::WithdrawOrigin::ensure_address_origin(&address, origin)?;
let address_account_id = T::AddressMapping::into_account_id(address);
T::Currency::transfer(
&address_account_id,
&destination,
value,
ExistenceRequirement::AllowDeath,
)?;
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight({
let without_base_extrinsic_weight = true;
T::GasWeightMapping::gas_to_weight(*gas_limit, without_base_extrinsic_weight)
})]
pub fn call(
origin: OriginFor<T>,
source: H160,
target: H160,
input: Vec<u8>,
value: U256,
gas_limit: u64,
max_fee_per_gas: U256,
max_priority_fee_per_gas: Option<U256>,
nonce: Option<U256>,
access_list: Vec<(H160, Vec<H256>)>,
) -> DispatchResultWithPostInfo {
T::CallOrigin::ensure_address_origin(&source, origin)?;
let is_transactional = true;
let validate = true;
let info = match T::Runner::call(
source,
target,
input,
value,
gas_limit,
Some(max_fee_per_gas),
max_priority_fee_per_gas,
nonce,
access_list,
is_transactional,
validate,
None,
None,
T::config(),
) {
Ok(info) => info,
Err(e) => {
return Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(e.weight),
pays_fee: Pays::Yes,
},
error: e.error.into(),
})
}
};
match info.exit_reason {
ExitReason::Succeed(_) => {
Pallet::<T>::deposit_event(Event::<T>::Executed { address: target });
}
_ => {
Pallet::<T>::deposit_event(Event::<T>::ExecutedFailed { address: target });
}
};
Ok(PostDispatchInfo {
actual_weight: {
let mut gas_to_weight = T::GasWeightMapping::gas_to_weight(
info.used_gas.standard.unique_saturated_into(),
true,
);
if let Some(weight_info) = info.weight_info {
if let Some(proof_size_usage) = weight_info.proof_size_usage {
*gas_to_weight.proof_size_mut() = proof_size_usage;
}
}
Some(gas_to_weight)
},
pays_fee: Pays::No,
})
}
#[pallet::call_index(2)]
#[pallet::weight({
let without_base_extrinsic_weight = true;
T::GasWeightMapping::gas_to_weight(*gas_limit, without_base_extrinsic_weight)
})]
pub fn create(
origin: OriginFor<T>,
source: H160,
init: Vec<u8>,
value: U256,
gas_limit: u64,
max_fee_per_gas: U256,
max_priority_fee_per_gas: Option<U256>,
nonce: Option<U256>,
access_list: Vec<(H160, Vec<H256>)>,
) -> DispatchResultWithPostInfo {
T::CallOrigin::ensure_address_origin(&source, origin)?;
let is_transactional = true;
let validate = true;
let info = match T::Runner::create(
source,
init,
value,
gas_limit,
Some(max_fee_per_gas),
max_priority_fee_per_gas,
nonce,
access_list,
is_transactional,
validate,
None,
None,
T::config(),
) {
Ok(info) => info,
Err(e) => {
return Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(e.weight),
pays_fee: Pays::Yes,
},
error: e.error.into(),
})
}
};
match info {
CreateInfo {
exit_reason: ExitReason::Succeed(_),
value: create_address,
..
} => {
Pallet::<T>::deposit_event(Event::<T>::Created {
address: create_address,
});
}
CreateInfo {
exit_reason: _,
value: create_address,
..
} => {
Pallet::<T>::deposit_event(Event::<T>::CreatedFailed {
address: create_address,
});
}
}
Ok(PostDispatchInfo {
actual_weight: {
let mut gas_to_weight = T::GasWeightMapping::gas_to_weight(
info.used_gas.standard.unique_saturated_into(),
true,
);
if let Some(weight_info) = info.weight_info {
if let Some(proof_size_usage) = weight_info.proof_size_usage {
*gas_to_weight.proof_size_mut() = proof_size_usage;
}
}
Some(gas_to_weight)
},
pays_fee: Pays::No,
})
}
#[pallet::call_index(3)]
#[pallet::weight({
let without_base_extrinsic_weight = true;
T::GasWeightMapping::gas_to_weight(*gas_limit, without_base_extrinsic_weight)
})]
pub fn create2(
origin: OriginFor<T>,
source: H160,
init: Vec<u8>,
salt: H256,
value: U256,
gas_limit: u64,
max_fee_per_gas: U256,
max_priority_fee_per_gas: Option<U256>,
nonce: Option<U256>,
access_list: Vec<(H160, Vec<H256>)>,
) -> DispatchResultWithPostInfo {
T::CallOrigin::ensure_address_origin(&source, origin)?;
let is_transactional = true;
let validate = true;
let info = match T::Runner::create2(
source,
init,
salt,
value,
gas_limit,
Some(max_fee_per_gas),
max_priority_fee_per_gas,
nonce,
access_list,
is_transactional,
validate,
None,
None,
T::config(),
) {
Ok(info) => info,
Err(e) => {
return Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(e.weight),
pays_fee: Pays::Yes,
},
error: e.error.into(),
})
}
};
match info {
CreateInfo {
exit_reason: ExitReason::Succeed(_),
value: create_address,
..
} => {
Pallet::<T>::deposit_event(Event::<T>::Created {
address: create_address,
});
}
CreateInfo {
exit_reason: _,
value: create_address,
..
} => {
Pallet::<T>::deposit_event(Event::<T>::CreatedFailed {
address: create_address,
});
}
}
Ok(PostDispatchInfo {
actual_weight: {
let mut gas_to_weight = T::GasWeightMapping::gas_to_weight(
info.used_gas.standard.unique_saturated_into(),
true,
);
if let Some(weight_info) = info.weight_info {
if let Some(proof_size_usage) = weight_info.proof_size_usage {
*gas_to_weight.proof_size_mut() = proof_size_usage;
}
}
Some(gas_to_weight)
},
pays_fee: Pays::No,
})
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Log { log: Log },
Created { address: H160 },
CreatedFailed { address: H160 },
Executed { address: H160 },
ExecutedFailed { address: H160 },
}
#[pallet::error]
pub enum Error<T> {
BalanceLow,
FeeOverflow,
PaymentOverflow,
WithdrawFailed,
GasPriceTooLow,
InvalidNonce,
GasLimitTooLow,
GasLimitTooHigh,
InvalidChainId,
InvalidSignature,
Reentrancy,
TransactionMustComeFromEOA,
Undefined,
CreateOriginNotAllowed,
}
impl<T> From<TransactionValidationError> for Error<T> {
fn from(validation_error: TransactionValidationError) -> Self {
match validation_error {
TransactionValidationError::GasLimitTooLow => Error::<T>::GasLimitTooLow,
TransactionValidationError::GasLimitTooHigh => Error::<T>::GasLimitTooHigh,
TransactionValidationError::BalanceTooLow => Error::<T>::BalanceLow,
TransactionValidationError::TxNonceTooLow => Error::<T>::InvalidNonce,
TransactionValidationError::TxNonceTooHigh => Error::<T>::InvalidNonce,
TransactionValidationError::GasPriceTooLow => Error::<T>::GasPriceTooLow,
TransactionValidationError::PriorityFeeTooHigh => Error::<T>::GasPriceTooLow,
TransactionValidationError::InvalidFeeInput => Error::<T>::GasPriceTooLow,
TransactionValidationError::InvalidChainId => Error::<T>::InvalidChainId,
TransactionValidationError::InvalidSignature => Error::<T>::InvalidSignature,
TransactionValidationError::UnknownError => Error::<T>::Undefined,
}
}
}
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T> {
pub accounts: BTreeMap<H160, GenesisAccount>,
#[serde(skip)]
pub _marker: PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T>
where
U256: UniqueSaturatedInto<BalanceOf<T>>,
{
fn build(&self) {
const MAX_ACCOUNT_NONCE: usize = 100;
for (address, account) in &self.accounts {
let account_id = T::AddressMapping::into_account_id(*address);
for _ in 0..min(
MAX_ACCOUNT_NONCE,
UniqueSaturatedInto::<usize>::unique_saturated_into(account.nonce),
) {
T::AccountProvider::inc_account_nonce(&account_id);
}
let _ = T::Currency::deposit_creating(
&account_id,
account.balance.unique_saturated_into(),
);
let _ = Pallet::<T>::create_account(*address, account.code.clone(), None);
for (index, value) in &account.storage {
<AccountStorages<T>>::insert(address, index, value);
}
}
}
}
#[pallet::storage]
pub type AccountCodes<T: Config> = StorageMap<_, Blake2_128Concat, H160, Vec<u8>, ValueQuery>;
#[pallet::storage]
pub type AccountCodesMetadata<T: Config> =
StorageMap<_, Blake2_128Concat, H160, CodeMetadata, OptionQuery>;
#[pallet::storage]
pub type AccountStorages<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>;
}
pub type AccountIdOf<T> = <<T as Config>::AccountProvider as AccountProvider>::AccountId;
pub type BalanceOf<T> = <<T as Config>::Currency as Currency<AccountIdOf<T>>>::Balance;
type NegativeImbalanceOf<C, T> = <C as Currency<AccountIdOf<T>>>::NegativeImbalance;
#[derive(
Debug,
Clone,
Copy,
Eq,
PartialEq,
Encode,
Decode,
TypeInfo,
MaxEncodedLen
)]
pub struct CodeMetadata {
pub size: u64,
pub hash: H256,
}
impl CodeMetadata {
pub fn from_code(code: &[u8]) -> Self {
let size = code.len() as u64;
let hash = H256::from(sp_io::hashing::keccak_256(code));
Self { size, hash }
}
}
pub trait EnsureAddressOrigin<OuterOrigin> {
type Success;
fn ensure_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<Self::Success, BadOrigin> {
Self::try_address_origin(address, origin).map_err(|_| BadOrigin)
}
fn try_address_origin(
address: &H160,
origin: OuterOrigin,
) -> Result<Self::Success, OuterOrigin>;
}
pub struct EnsureAddressSame;
impl<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAddressSame
where
OuterOrigin: Into<Result<RawOrigin<H160>, OuterOrigin>> + From<RawOrigin<H160>>,
{
type Success = H160;
fn try_address_origin(address: &H160, origin: OuterOrigin) -> Result<H160, OuterOrigin> {
origin.into().and_then(|o| match o {
RawOrigin::Signed(who) if &who == address => Ok(who),
r => Err(OuterOrigin::from(r)),
})
}
}
pub struct EnsureAddressRoot<AccountId>(core::marker::PhantomData<AccountId>);
impl<OuterOrigin, AccountId> EnsureAddressOrigin<OuterOrigin> for EnsureAddressRoot<AccountId>
where
OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>> + From<RawOrigin<AccountId>>,
{
type Success = ();
fn try_address_origin(_address: &H160, origin: OuterOrigin) -> Result<(), OuterOrigin> {
origin.into().and_then(|o| match o {
RawOrigin::Root => Ok(()),
r => Err(OuterOrigin::from(r)),
})
}
}
pub struct EnsureAddressNever<AccountId>(core::marker::PhantomData<AccountId>);
impl<OuterOrigin, AccountId> EnsureAddressOrigin<OuterOrigin> for EnsureAddressNever<AccountId> {
type Success = AccountId;
fn try_address_origin(_address: &H160, origin: OuterOrigin) -> Result<AccountId, OuterOrigin> {
Err(origin)
}
}
pub struct EnsureAddressTruncated;
impl<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAddressTruncated
where
OuterOrigin: Into<Result<RawOrigin<AccountId32>, OuterOrigin>> + From<RawOrigin<AccountId32>>,
{
type Success = AccountId32;
fn try_address_origin(address: &H160, origin: OuterOrigin) -> Result<AccountId32, OuterOrigin> {
origin.into().and_then(|o| match o {
RawOrigin::Signed(who) if AsRef::<[u8; 32]>::as_ref(&who)[0..20] == address[0..20] => {
Ok(who)
}
r => Err(OuterOrigin::from(r)),
})
}
}
pub struct EnsureAccountId20;
impl<OuterOrigin> EnsureAddressOrigin<OuterOrigin> for EnsureAccountId20
where
OuterOrigin: Into<Result<RawOrigin<AccountId20>, OuterOrigin>> + From<RawOrigin<AccountId20>>,
{
type Success = AccountId20;
fn try_address_origin(address: &H160, origin: OuterOrigin) -> Result<AccountId20, OuterOrigin> {
let acc: AccountId20 = AccountId20::from(*address);
origin.into().and_then(|o| match o {
RawOrigin::Signed(who) if who == acc => Ok(who),
r => Err(OuterOrigin::from(r)),
})
}
}
pub trait EnsureCreateOrigin<T> {
fn check_create_origin(address: &H160) -> Result<(), Error<T>>;
}
pub struct EnsureAllowedCreateAddress<AddressGetter>(core::marker::PhantomData<AddressGetter>);
impl<AddressGetter, T: Config> EnsureCreateOrigin<T> for EnsureAllowedCreateAddress<AddressGetter>
where
AddressGetter: Get<Vec<H160>>,
{
fn check_create_origin(address: &H160) -> Result<(), Error<T>> {
if !AddressGetter::get().contains(address) {
return Err(Error::<T>::CreateOriginNotAllowed);
}
Ok(())
}
}
impl<T> EnsureCreateOrigin<T> for () {
fn check_create_origin(_address: &H160) -> Result<(), Error<T>> {
Ok(())
}
}
pub trait AddressMapping<A> {
fn into_account_id(address: H160) -> A;
}
pub struct IdentityAddressMapping;
impl<T: From<H160>> AddressMapping<T> for IdentityAddressMapping {
fn into_account_id(address: H160) -> T {
address.into()
}
}
pub struct HashedAddressMapping<H>(core::marker::PhantomData<H>);
impl<H: Hasher<Out = H256>> AddressMapping<AccountId32> for HashedAddressMapping<H> {
fn into_account_id(address: H160) -> AccountId32 {
let mut data = [0u8; 24];
data[0..4].copy_from_slice(b"evm:");
data[4..24].copy_from_slice(&address[..]);
let hash = H::hash(&data);
AccountId32::from(Into::<[u8; 32]>::into(hash))
}
}
pub trait BlockHashMapping {
fn block_hash(number: u32) -> H256;
}
pub struct SubstrateBlockHashMapping<T>(core::marker::PhantomData<T>);
impl<T: Config> BlockHashMapping for SubstrateBlockHashMapping<T> {
fn block_hash(number: u32) -> H256 {
let number = <NumberFor<T::Block>>::from(number);
H256::from_slice(frame_system::Pallet::<T>::block_hash(number).as_ref())
}
}
pub trait GasWeightMapping {
fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight;
fn weight_to_gas(weight: Weight) -> u64;
}
pub trait FixedGasWeightMappingAssociatedTypes {
type WeightPerGas: Get<Weight>;
type BlockWeights: Get<frame_system::limits::BlockWeights>;
type GasLimitPovSizeRatio: Get<u64>;
}
impl<T: Config> FixedGasWeightMappingAssociatedTypes for T {
type WeightPerGas = T::WeightPerGas;
type BlockWeights = T::BlockWeights;
type GasLimitPovSizeRatio = T::GasLimitPovSizeRatio;
}
pub struct FixedGasWeightMapping<T>(core::marker::PhantomData<T>);
impl<T> GasWeightMapping for FixedGasWeightMapping<T>
where
T: FixedGasWeightMappingAssociatedTypes,
{
fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight {
let mut weight = T::WeightPerGas::get().saturating_mul(gas);
if without_base_weight {
weight = weight.saturating_sub(
T::BlockWeights::get()
.get(frame_support::dispatch::DispatchClass::Normal)
.base_extrinsic,
);
}
let ratio = T::GasLimitPovSizeRatio::get();
if ratio > 0 {
let proof_size = gas.saturating_div(ratio);
*weight.proof_size_mut() = proof_size;
}
weight
}
fn weight_to_gas(weight: Weight) -> u64 {
weight.div(T::WeightPerGas::get().ref_time()).ref_time()
}
}
static CANCUN_CONFIG: EvmConfig = EvmConfig::cancun();
impl<T: Config> Pallet<T> {
pub fn is_account_empty(address: &H160) -> bool {
let (account, _) = Self::account_basic(address);
let code_len = <AccountCodes<T>>::decode_len(address).unwrap_or(0);
account.nonce == U256::zero() && account.balance == U256::zero() && code_len == 0
}
pub fn iter_account_storages(address: &H160) -> KeyPrefixIterator<H256> {
<AccountStorages<T>>::iter_key_prefix(address)
}
pub fn remove_account_if_empty(address: &H160) {
if Self::is_account_empty(address) {
Self::remove_account(address);
}
}
pub fn remove_account(address: &H160) {
if <AccountCodes<T>>::contains_key(address) {
let account_id = T::AddressMapping::into_account_id(*address);
T::AccountProvider::remove_account(&account_id);
}
<AccountCodes<T>>::remove(address);
<AccountCodesMetadata<T>>::remove(address);
let _ = <AccountStorages<T>>::clear_prefix(address, u32::MAX, None);
}
pub fn create_account(
address: H160,
code: Vec<u8>,
caller: Option<H160>,
) -> Result<(), ExitError> {
if let Some(caller_address) = caller {
T::CreateInnerOriginFilter::check_create_origin(&caller_address).map_err(|e| {
let error: &'static str = e.into();
ExitError::Other(Cow::Borrowed(error))
})?;
}
if code.is_empty() {
return Ok(());
}
if !<AccountCodes<T>>::contains_key(address) {
let account_id = T::AddressMapping::into_account_id(address);
T::AccountProvider::create_account(&account_id);
}
let meta = CodeMetadata::from_code(&code);
<AccountCodesMetadata<T>>::insert(address, meta);
<AccountCodes<T>>::insert(address, code);
Ok(())
}
pub fn account_code_metadata(address: H160) -> CodeMetadata {
if let Some(meta) = <AccountCodesMetadata<T>>::get(address) {
return meta;
}
let code = <AccountCodes<T>>::get(address);
if code.is_empty() {
const EMPTY_CODE_HASH: [u8; 32] = hex_literal::hex!(
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
);
return CodeMetadata {
size: 0,
hash: EMPTY_CODE_HASH.into(),
};
}
let meta = CodeMetadata::from_code(&code);
<AccountCodesMetadata<T>>::insert(address, meta);
meta
}
pub fn account_basic(address: &H160) -> (Account, frame_support::weights::Weight) {
let account_id = T::AddressMapping::into_account_id(*address);
let nonce = T::AccountProvider::account_nonce(&account_id);
let balance =
T::Currency::reducible_balance(&account_id, Preservation::Preserve, Fortitude::Polite);
(
Account {
nonce: U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(nonce)),
balance: U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(balance)),
},
T::DbWeight::get().reads(2),
)
}
pub fn find_author() -> H160 {
let digest = <frame_system::Pallet<T>>::digest();
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
T::FindAuthor::find_author(pre_runtime_digests).unwrap_or_default()
}
}
pub trait OnChargeEVMTransaction<T: Config> {
type LiquidityInfo: Default;
fn withdraw_fee(who: &H160, fee: U256) -> Result<Self::LiquidityInfo, Error<T>>;
fn correct_and_deposit_fee(
who: &H160,
corrected_fee: U256,
base_fee: U256,
already_withdrawn: Self::LiquidityInfo,
) -> Self::LiquidityInfo;
fn pay_priority_fee(tip: Self::LiquidityInfo);
}
pub struct EVMCurrencyAdapter<C, OU>(core::marker::PhantomData<(C, OU)>);
impl<T, C, OU> OnChargeEVMTransaction<T> for EVMCurrencyAdapter<C, OU>
where
T: Config,
C: Currency<AccountIdOf<T>>,
C::PositiveImbalance:
Imbalance<<C as Currency<AccountIdOf<T>>>::Balance, Opposite = C::NegativeImbalance>,
C::NegativeImbalance:
Imbalance<<C as Currency<AccountIdOf<T>>>::Balance, Opposite = C::PositiveImbalance>,
OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
U256: UniqueSaturatedInto<<C as Currency<AccountIdOf<T>>>::Balance>,
{
type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
fn withdraw_fee(who: &H160, fee: U256) -> Result<Self::LiquidityInfo, Error<T>> {
if fee.is_zero() {
return Ok(None);
}
let account_id = T::AddressMapping::into_account_id(*who);
let imbalance = C::withdraw(
&account_id,
fee.unique_saturated_into(),
WithdrawReasons::FEE,
ExistenceRequirement::AllowDeath,
)
.map_err(|_| Error::<T>::BalanceLow)?;
Ok(Some(imbalance))
}
fn correct_and_deposit_fee(
who: &H160,
corrected_fee: U256,
base_fee: U256,
already_withdrawn: Self::LiquidityInfo,
) -> Self::LiquidityInfo {
if let Some(paid) = already_withdrawn {
let account_id = T::AddressMapping::into_account_id(*who);
let refund_amount = paid
.peek()
.saturating_sub(corrected_fee.unique_saturated_into());
let refund_imbalance = C::deposit_into_existing(&account_id, refund_amount)
.unwrap_or_else(|_| C::PositiveImbalance::zero());
let refund_imbalance = if C::minimum_balance().is_zero()
&& refund_amount > C::Balance::zero()
&& C::total_balance(&account_id).is_zero()
{
match C::make_free_balance_be(&account_id, refund_amount) {
SignedImbalance::Positive(p) => p,
_ => C::PositiveImbalance::zero(),
}
} else {
refund_imbalance
};
let adjusted_paid = paid
.offset(refund_imbalance)
.same()
.unwrap_or_else(|_| C::NegativeImbalance::zero());
let (base_fee, tip) = adjusted_paid.split(base_fee.unique_saturated_into());
OU::on_unbalanced(base_fee);
return Some(tip);
}
None
}
fn pay_priority_fee(tip: Self::LiquidityInfo) {
if let Some(tip) = tip {
let account_id = T::AddressMapping::into_account_id(<Pallet<T>>::find_author());
let _ = C::deposit_into_existing(&account_id, tip.peek());
}
}
}
pub struct EVMFungibleAdapter<F, OU>(core::marker::PhantomData<(F, OU)>);
impl<T, F, OU> OnChargeEVMTransaction<T> for EVMFungibleAdapter<F, OU>
where
T: Config,
F: Balanced<AccountIdOf<T>>,
OU: OnUnbalanced<Credit<AccountIdOf<T>, F>>,
U256: UniqueSaturatedInto<<F as Inspect<AccountIdOf<T>>>::Balance>,
{
type LiquidityInfo = Option<Credit<AccountIdOf<T>, F>>;
fn withdraw_fee(who: &H160, fee: U256) -> Result<Self::LiquidityInfo, Error<T>> {
if fee.is_zero() {
return Ok(None);
}
let account_id = T::AddressMapping::into_account_id(*who);
let imbalance = F::withdraw(
&account_id,
fee.unique_saturated_into(),
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)
.map_err(|_| Error::<T>::BalanceLow)?;
Ok(Some(imbalance))
}
fn correct_and_deposit_fee(
who: &H160,
corrected_fee: U256,
base_fee: U256,
already_withdrawn: Self::LiquidityInfo,
) -> Self::LiquidityInfo {
if let Some(paid) = already_withdrawn {
let account_id = T::AddressMapping::into_account_id(*who);
let refund_amount = paid
.peek()
.saturating_sub(corrected_fee.unique_saturated_into());
let refund_imbalance = F::deposit(&account_id, refund_amount, Precision::BestEffort)
.unwrap_or_else(|_| Debt::<AccountIdOf<T>, F>::zero());
let adjusted_paid = paid
.offset(refund_imbalance)
.same()
.unwrap_or_else(|_| Credit::<AccountIdOf<T>, F>::zero());
let (base_fee, tip) = adjusted_paid.split(base_fee.unique_saturated_into());
OU::on_unbalanced(base_fee);
return Some(tip);
}
None
}
fn pay_priority_fee(tip: Self::LiquidityInfo) {
if let Some(tip) = tip {
let account_id = T::AddressMapping::into_account_id(<Pallet<T>>::find_author());
let _ = F::deposit(&account_id, tip.peek(), Precision::BestEffort);
}
}
}
impl<T> OnChargeEVMTransaction<T> for ()
where
T: Config,
T::Currency: Balanced<AccountIdOf<T>>,
U256: UniqueSaturatedInto<<<T as Config>::Currency as Inspect<AccountIdOf<T>>>::Balance>,
{
type LiquidityInfo = Option<Credit<AccountIdOf<T>, T::Currency>>;
fn withdraw_fee(who: &H160, fee: U256) -> Result<Self::LiquidityInfo, Error<T>> {
EVMFungibleAdapter::<T::Currency, ()>::withdraw_fee(who, fee)
}
fn correct_and_deposit_fee(
who: &H160,
corrected_fee: U256,
base_fee: U256,
already_withdrawn: Self::LiquidityInfo,
) -> Self::LiquidityInfo {
<EVMFungibleAdapter<T::Currency, ()> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(
who,
corrected_fee,
base_fee,
already_withdrawn,
)
}
fn pay_priority_fee(tip: Self::LiquidityInfo) {
<EVMFungibleAdapter<T::Currency, ()> as OnChargeEVMTransaction<T>>::pay_priority_fee(tip);
}
}
pub trait OnCreate<T> {
fn on_create(owner: H160, contract: H160);
}
impl<T> OnCreate<T> for () {
fn on_create(_owner: H160, _contract: H160) {}
}
#[impl_for_tuples(1, 12)]
impl<T> OnCreate<T> for Tuple {
fn on_create(owner: H160, contract: H160) {
for_tuples!(#(
Tuple::on_create(owner, contract);
)*)
}
}
pub struct FrameSystemAccountProvider<T>(core::marker::PhantomData<T>);
impl<T: frame_system::Config> AccountProvider for FrameSystemAccountProvider<T> {
type AccountId = T::AccountId;
type Nonce = T::Nonce;
fn account_nonce(who: &Self::AccountId) -> Self::Nonce {
frame_system::Pallet::<T>::account_nonce(who)
}
fn inc_account_nonce(who: &Self::AccountId) {
frame_system::Pallet::<T>::inc_account_nonce(who)
}
fn create_account(who: &Self::AccountId) {
let _ = frame_system::Pallet::<T>::inc_sufficients(who);
}
fn remove_account(who: &Self::AccountId) {
let _ = frame_system::Pallet::<T>::dec_sufficients(who);
}
}