From f0a75e47d1a43702a6e74e1d0cc21ffd78f41c34 Mon Sep 17 00:00:00 2001 From: Trubnikov Sergey Date: Mon, 10 Apr 2023 11:15:28 +0000 Subject: [PATCH] feat: Move sponsorship from frontier --- pallets/evm-transaction-payment/Cargo.toml | 1 + pallets/evm-transaction-payment/src/lib.rs | 191 ++++++++++++++++++++- runtime/common/config/ethereum.rs | 6 +- tests/src/eth/contractSponsoring.test.ts | 2 +- 4 files changed, 189 insertions(+), 11 deletions(-) diff --git a/pallets/evm-transaction-payment/Cargo.toml b/pallets/evm-transaction-payment/Cargo.toml index 7a46caf796..a339d7a363 100644 --- a/pallets/evm-transaction-payment/Cargo.toml +++ b/pallets/evm-transaction-payment/Cargo.toml @@ -14,6 +14,7 @@ fp-evm = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-evm = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } diff --git a/pallets/evm-transaction-payment/src/lib.rs b/pallets/evm-transaction-payment/src/lib.rs index 6bf0976fab..4eeb9d97d6 100644 --- a/pallets/evm-transaction-payment/src/lib.rs +++ b/pallets/evm-transaction-payment/src/lib.rs @@ -19,13 +19,20 @@ #![deny(missing_docs)] use core::marker::PhantomData; -use fp_evm::WithdrawReason; -use frame_support::traits::IsSubType; +use fp_evm::{CheckEvmTransaction, FeeCalculator, WithdrawReason, InvalidEvmTransactionError}; +use frame_support::{ + storage::with_transaction, + traits::{IsSubType, Currency, Imbalance, OnUnbalanced}, +}; pub use pallet::*; -use pallet_evm::{account::CrossAccountId, EnsureAddressOrigin}; +use pallet_evm::{ + account::CrossAccountId, EnsureAddressOrigin, TransactionValidate, OnChargeEVMTransaction, + NegativeImbalanceOf, +}; use sp_core::{H160, U256}; use sp_runtime::{TransactionOutcome, DispatchError}; use up_sponsorship::SponsorshipHandler; +use sp_arithmetic::traits::UniqueSaturatedInto; #[frame_support::pallet] pub mod pallet { @@ -54,16 +61,13 @@ pub mod pallet { pub struct Pallet(_); } -/// Implements [`fp_evm::TransactionValidityHack`], which provides sponsor address to pallet-evm -pub struct TransactionValidityHack(PhantomData<*const T>); -impl fp_evm::TransactionValidityHack for TransactionValidityHack { - fn who_pays_fee( +fn who_pays_fee( origin: H160, max_fee: U256, reason: &WithdrawReason, ) -> Option { match reason { - WithdrawReason::Call { target, input } => { + WithdrawReason::Call { target, input, .. } => { let origin_sub = T::CrossAccountId::from_eth(origin); let call_context = CallContext { contract_address: *target, @@ -75,6 +79,43 @@ impl fp_evm::TransactionValidityHack for Transacti _ => None, } } + +fn get_sponsor( + source: H160, + max_fee_per_gas: Option, + gas_limit: U256, + reason: &WithdrawReason, + is_transactional: bool, + is_check: bool, +) -> Option { + let accept_gas_fee = |gas_fee| { + let (base_fee, _) = T::FeeCalculator::min_gas_price(); + base_fee <= gas_fee && gas_fee <= base_fee * 21 / 10 + }; + let (max_fee_per_gas, may_sponsor) = match (max_fee_per_gas, is_transactional) { + (Some(max_fee_per_gas), _) => (max_fee_per_gas, accept_gas_fee(max_fee_per_gas)), + // Gas price check is skipped for non-transactional calls that don't + // define a `max_fee_per_gas` input. + (None, false) => (Default::default(), true), + _ => return None, + }; + + let max_fee = max_fee_per_gas.saturating_mul(gas_limit); + + // #[cfg(feature = "debug-logging")] + // log::trace!(target: "sponsoring", "checking who will pay fee for {:?} {:?}", source, reason); + with_transaction(|| { + let result = may_sponsor + .then(|| who_pays_fee::(source, max_fee, reason)) + .flatten(); + if is_check { + TransactionOutcome::Rollback(Ok::<_, DispatchError>(result)) + } else { + TransactionOutcome::Commit(Ok(result)) + } + }) + .ok() + .flatten() } /// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call) @@ -121,3 +162,137 @@ where } } } + +/// Bla bla bla Mr. Freeman +pub struct TransactionValidity>( + PhantomData<(T,E)> +); +impl> TransactionValidate + for TransactionValidity +{ + fn check_evm_transaction<'a>( + v: CheckEvmTransaction<'a, E>, + origin: &T::CrossAccountId, + ) -> Result, E> { + let who = &v.who; + let max_fee_per_gas = Some(v.transaction_fee_input()?.0); + let gas_limit = v.transaction.gas_limit; + let reason = if let Some(to) = v.transaction.to { + WithdrawReason::Call { + target: to, + input: v.transaction.input.clone(), + max_fee_per_gas, + gas_limit, + is_transactional: v.config.is_transactional, + is_check: true, + } + } else { + WithdrawReason::Create + }; + let sponsor = get_sponsor::( + *origin.as_eth(), + max_fee_per_gas, + gas_limit, + &reason, + v.config.is_transactional, + true, + ) + .as_ref() + .map(pallet_evm::Pallet::::account_basic_by_id) + .map(|v| v.0); + + let fee = max_fee_per_gas.unwrap().saturating_mul(v.transaction.gas_limit); + if let Some(sponsor) = sponsor.as_ref() { + if who.balance < v.transaction.value || sponsor.balance < fee { + return Err(InvalidEvmTransactionError::BalanceTooLow.into()); + } + } else { + let total_payment = v.transaction.value.saturating_add(fee); + if who.balance < total_payment { + return Err(InvalidEvmTransactionError::BalanceTooLow.into()); + } + } + + let mut v = v; + let who = sponsor.unwrap_or_else(|| v.who.clone()); + v.who.balance = who.balance; + Ok(v) + } +} + +/// Implements the transaction payment for a pallet implementing the `Currency` +/// trait (eg. the pallet_balances) using an unbalance handler (implementing +/// `OnUnbalanced`). +/// Similar to `CurrencyAdapter` of `pallet_transaction_payment` +pub struct WrappedEVMCurrencyAdapter(sp_std::marker::PhantomData<(C, OU)>); +impl OnChargeEVMTransaction for WrappedEVMCurrencyAdapter +where + T: Config, + C: Currency<::AccountId>, + C::PositiveImbalance: Imbalance< + ::AccountId>>::Balance, + Opposite = C::NegativeImbalance, + >, + C::NegativeImbalance: Imbalance< + ::AccountId>>::Balance, + Opposite = C::PositiveImbalance, + >, + OU: OnUnbalanced>, + U256: UniqueSaturatedInto<::AccountId>>::Balance>, +{ + // Kept type as Option to satisfy bound of Default + type LiquidityInfo = (Option>, Option); + + fn withdraw_fee( + who: &T::CrossAccountId, + reason: WithdrawReason, + fee: U256, + ) -> Result> { + let sponsor = match reason { + WithdrawReason::Call { + max_fee_per_gas, + gas_limit, + is_transactional, + is_check, + .. + } => get_sponsor::( + *who.as_eth(), + max_fee_per_gas, + gas_limit, + &reason, + is_transactional, + is_check, + ), + _ => None, + }; + + let who = sponsor.as_ref().unwrap_or(who); + as OnChargeEVMTransaction>::withdraw_fee( + who, reason, fee, + ) + .map(|li| (li, sponsor)) + } + + fn correct_and_deposit_fee( + who: &T::CrossAccountId, + corrected_fee: U256, + base_fee: U256, + already_withdrawn: Self::LiquidityInfo, + ) -> Self::LiquidityInfo { + let (already_withdrawn, sponsor) = already_withdrawn; + let who = sponsor.as_ref().unwrap_or(who); + ( + as OnChargeEVMTransaction>::correct_and_deposit_fee( + who, + corrected_fee, + base_fee, + already_withdrawn, + ).map(|ni| ni), + None + ) + } + + fn pay_priority_fee(tip: Self::LiquidityInfo) { + as OnChargeEVMTransaction>::pay_priority_fee(tip.0) + } +} diff --git a/runtime/common/config/ethereum.rs b/runtime/common/config/ethereum.rs index d18dc539f3..b78622bfd9 100644 --- a/runtime/common/config/ethereum.rs +++ b/runtime/common/config/ethereum.rs @@ -78,9 +78,11 @@ impl pallet_evm::Config for Runtime { type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; type ChainId = ChainId; type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; - type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack; + type OnChargeTransaction = + pallet_evm_transaction_payment::WrappedEVMCurrencyAdapter; type FindAuthor = EthereumFindAuthor; + type TransactionValidityOnChain> = + pallet_evm_transaction_payment::TransactionValidity; } impl pallet_evm_migration::Config for Runtime { diff --git a/tests/src/eth/contractSponsoring.test.ts b/tests/src/eth/contractSponsoring.test.ts index 37e2acb92c..d30bfeb600 100644 --- a/tests/src/eth/contractSponsoring.test.ts +++ b/tests/src/eth/contractSponsoring.test.ts @@ -482,7 +482,7 @@ describe('Sponsoring Fee Limit', () => { before(async () => { await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); - [alice] = await helper.arrange.createAccounts([100n], donor); + [alice] = await helper.arrange.createAccounts([1000n], donor); }); });