Skip to content

Commit

Permalink
feat: Move sponsorship from frontier
Browse files Browse the repository at this point in the history
  • Loading branch information
bugrazoid committed Apr 10, 2023
1 parent bc18b50 commit f0a75e4
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 11 deletions.
1 change: 1 addition & 0 deletions pallets/evm-transaction-payment/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
191 changes: 183 additions & 8 deletions pallets/evm-transaction-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -54,16 +61,13 @@ pub mod pallet {
pub struct Pallet<T>(_);
}

/// Implements [`fp_evm::TransactionValidityHack`], which provides sponsor address to pallet-evm
pub struct TransactionValidityHack<T: Config>(PhantomData<*const T>);
impl<T: Config> fp_evm::TransactionValidityHack<T::CrossAccountId> for TransactionValidityHack<T> {
fn who_pays_fee(
fn who_pays_fee<T: Config>(
origin: H160,
max_fee: U256,
reason: &WithdrawReason,
) -> Option<T::CrossAccountId> {
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,
Expand All @@ -75,6 +79,43 @@ impl<T: Config> fp_evm::TransactionValidityHack<T::CrossAccountId> for Transacti
_ => None,
}
}

fn get_sponsor<T: Config>(
source: H160,
max_fee_per_gas: Option<U256>,
gas_limit: U256,
reason: &WithdrawReason,
is_transactional: bool,
is_check: bool,
) -> Option<T::CrossAccountId> {
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::<T>(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)
Expand Down Expand Up @@ -121,3 +162,137 @@ where
}
}
}

/// Bla bla bla Mr. Freeman
pub struct TransactionValidity<T: Config, E: From<fp_evm::InvalidEvmTransactionError>>(
PhantomData<(T,E)>
);
impl<T: Config, E: From<fp_evm::InvalidEvmTransactionError>> TransactionValidate<T,E>
for TransactionValidity<T, E>
{
fn check_evm_transaction<'a>(
v: CheckEvmTransaction<'a, E>,
origin: &T::CrossAccountId,
) -> Result<CheckEvmTransaction<'a, E>, 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::<T>(
*origin.as_eth(),
max_fee_per_gas,
gas_limit,
&reason,
v.config.is_transactional,
true,
)
.as_ref()
.map(pallet_evm::Pallet::<T>::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<C, OU>(sp_std::marker::PhantomData<(C, OU)>);
impl<T, C, OU> OnChargeEVMTransaction<T> for WrappedEVMCurrencyAdapter<C, OU>
where
T: Config,
C: Currency<<T as frame_system::Config>::AccountId>,
C::PositiveImbalance: Imbalance<
<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
Opposite = C::NegativeImbalance,
>,
C::NegativeImbalance: Imbalance<
<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
Opposite = C::PositiveImbalance,
>,
OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
U256: UniqueSaturatedInto<<C as Currency<<T as frame_system::Config>::AccountId>>::Balance>,
{
// Kept type as Option to satisfy bound of Default
type LiquidityInfo = (Option<NegativeImbalanceOf<C, T>>, Option<T::CrossAccountId>);

fn withdraw_fee(
who: &T::CrossAccountId,
reason: WithdrawReason,
fee: U256,
) -> Result<Self::LiquidityInfo, pallet_evm::Error<T>> {
let sponsor = match reason {
WithdrawReason::Call {
max_fee_per_gas,
gas_limit,
is_transactional,
is_check,
..
} => get_sponsor::<T>(
*who.as_eth(),
max_fee_per_gas,
gas_limit,
&reason,
is_transactional,
is_check,
),
_ => None,
};

let who = sponsor.as_ref().unwrap_or(who);
<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::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);
(
<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(
who,
corrected_fee,
base_fee,
already_withdrawn,
).map(|ni| ni),
None
)
}

fn pay_priority_fee(tip: Self::LiquidityInfo) {
<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::pay_priority_fee(tip.0)
}
}
6 changes: 4 additions & 2 deletions runtime/common/config/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@ impl pallet_evm::Config for Runtime {
type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
type ChainId = ChainId;
type Runner = pallet_evm::runner::stack::Runner<Self>;
type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
type OnChargeTransaction =
pallet_evm_transaction_payment::WrappedEVMCurrencyAdapter<Balances, DealWithFees>;
type FindAuthor = EthereumFindAuthor<Aura>;
type TransactionValidityOnChain<E: From<fp_evm::InvalidEvmTransactionError>> =
pallet_evm_transaction_payment::TransactionValidity<Self, E>;
}

impl pallet_evm_migration::Config for Runtime {
Expand Down
2 changes: 1 addition & 1 deletion tests/src/eth/contractSponsoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down

0 comments on commit f0a75e4

Please sign in to comment.