diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index 6d81f90574..eca47f0ee1 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -77,11 +77,12 @@ use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo}, storage::{child::KillStorageResult, KeyPrefixIterator}, traits::{ + fungible::{Balanced, Credit, Debt}, tokens::{ currency::Currency, fungible::Inspect, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, - ExistenceRequirement, Fortitude, Preservation, WithdrawReasons, + ExistenceRequirement, Fortitude, Precision, Preservation, WithdrawReasons, }, FindAuthor, Get, Time, }, @@ -1048,6 +1049,79 @@ where } } } +/// Implements transaction payment for a pallet implementing the [`fungible`] +/// trait (eg. pallet_balances) using an unbalance handler (implementing +/// [`OnUnbalanced`]). +/// +/// Equivalent of `EVMCurrencyAdapter` but for fungible traits. Similar to `FungibleAdapter` of +/// `pallet_transaction_payment` +pub struct EVMFungibleAdapter(sp_std::marker::PhantomData<(F, OU)>); + +impl OnChargeEVMTransaction for EVMFungibleAdapter +where + T: Config, + F: Balanced, + OU: OnUnbalanced>, + U256: UniqueSaturatedInto<::AccountId>>::Balance>, +{ + // Kept type as Option to satisfy bound of Default + type LiquidityInfo = Option>; + + fn withdraw_fee(who: &H160, fee: U256) -> Result> { + 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::::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); + + // Calculate how much refund we should return + let refund_amount = paid + .peek() + .saturating_sub(corrected_fee.unique_saturated_into()); + // refund to the account that paid the fees. + let refund_imbalance = F::deposit(&account_id, refund_amount, Precision::BestEffort) + .unwrap_or_else(|_| Debt::::zero()); + + // merge the imbalance caused by paying the fees and refunding parts of it again. + let adjusted_paid = paid + .offset(refund_imbalance) + .same() + .unwrap_or_else(|_| Credit::::zero()); + + let (base_fee, tip) = adjusted_paid.split(base_fee.unique_saturated_into()); + // Handle base fee. Can be either burned, rationed, etc ... + OU::on_unbalanced(base_fee); + return Some(tip); + } + None + } + + fn pay_priority_fee(tip: Self::LiquidityInfo) { + // Default Ethereum behaviour: issue the tip to the block author. + if let Some(tip) = tip { + let account_id = T::AddressMapping::into_account_id(>::find_author()); + let _ = F::deposit(&account_id, tip.peek(), Precision::BestEffort); + } + } +} /// Implementation for () does not specify what to do with imbalance impl OnChargeEVMTransaction for ()