git.delta.rocks / unique-network / refs/commits / 470ffd5ce9b5

difftreelog

source

pallets/evm-transaction-payment/src/lib.rs8.8 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use core::marker::PhantomData;2223use fp_evm::{CheckEvmTransaction, FeeCalculator, TransactionValidationError, WithdrawReason};24use frame_support::{25	storage::with_transaction,26	traits::{Currency, Imbalance, IsSubType, OnUnbalanced},27};28pub use pallet::*;29use pallet_evm::{30	account::CrossAccountId, EnsureAddressOrigin, NegativeImbalanceOf, OnChargeEVMTransaction,31	OnCheckEvmTransaction,32};33use sp_core::{H160, U256};34use sp_runtime::{traits::UniqueSaturatedInto, DispatchError, TransactionOutcome};35use up_sponsorship::SponsorshipHandler;3637#[frame_support::pallet]38pub mod pallet {39	use sp_std::vec::Vec;4041	use super::*;4243	/// Contains call data44	pub struct CallContext {45		/// Contract address46		pub contract_address: H160,47		/// Transaction data48		pub input: Vec<u8>,49		/// Max fee for transaction - gasLimit * gasPrice50		pub max_fee: U256,51	}5253	#[pallet::config]54	pub trait Config: frame_system::Config + pallet_evm::Config {55		/// Loosly-coupled handlers for evm call sponsoring56		type EvmSponsorshipHandler: SponsorshipHandler<Self::CrossAccountId, CallContext>;57	}5859	#[pallet::pallet]60	pub struct Pallet<T>(_);61}6263fn who_pays_fee<T: Config>(64	origin: H160,65	max_fee: U256,66	reason: &WithdrawReason,67) -> Option<T::CrossAccountId> {68	match reason {69		WithdrawReason::Call { target, input, .. } => {70			let origin_sub = T::CrossAccountId::from_eth(origin);71			let call_context = CallContext {72				contract_address: *target,73				input: input.clone(),74				max_fee,75			};76			T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context)77		}78		_ => None,79	}80}8182fn get_sponsor<T: Config>(83	source: H160,84	max_fee_per_gas: Option<U256>,85	gas_limit: u64,86	reason: &WithdrawReason,87	is_transactional: bool,88	is_check: bool,89) -> Option<T::CrossAccountId> {90	let accept_gas_fee = |gas_fee| {91		let (base_fee, _) = T::FeeCalculator::min_gas_price();92		// Metamask specifies base fee twice as much as chain reported minGasPrice93		// But we allow further leeway (why?), sponsored base_fee to be 2.1*minGasPrice, thus 21/10.94		base_fee <= gas_fee && gas_fee <= base_fee * 21 / 1095	};96	let (max_fee_per_gas, may_sponsor) = match (max_fee_per_gas, is_transactional) {97		(Some(max_fee_per_gas), _) => (max_fee_per_gas, accept_gas_fee(max_fee_per_gas)),98		// Gas price check is skipped for non-transactional calls that don't99		// define a `max_fee_per_gas` input.100		(None, false) => (Default::default(), true),101		_ => return None,102	};103104	let max_fee = max_fee_per_gas.saturating_mul(gas_limit.into());105106	// #[cfg(feature = "debug-logging")]107	// log::trace!(target: "sponsoring", "checking who will pay fee for {:?} {:?}", source, reason);108	with_transaction(|| {109		let result = may_sponsor110			.then(|| who_pays_fee::<T>(source, max_fee, reason))111			.flatten();112		if is_check {113			TransactionOutcome::Rollback(Ok::<_, DispatchError>(result))114		} else {115			TransactionOutcome::Commit(Ok(result))116		}117	})118	.ok()119	.flatten()120}121/// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call)122pub struct BridgeSponsorshipHandler<T>(PhantomData<T>);123impl<T, C> SponsorshipHandler<T::AccountId, C> for BridgeSponsorshipHandler<T>124where125	T: Config + pallet_evm::Config,126	C: IsSubType<pallet_evm::Call<T>>,127{128	fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {129		match call.is_sub_type()? {130			pallet_evm::Call::call {131				source,132				target,133				input,134				gas_limit,135				max_fee_per_gas,136				..137			} => {138				let _ = T::CallOrigin::ensure_address_origin(139					source,140					<frame_system::RawOrigin<T::AccountId>>::Signed(who.clone()).into(),141				)142				.ok()?;143				let who = T::CrossAccountId::from_sub(who.clone());144				let max_fee = max_fee_per_gas.saturating_mul((*gas_limit).into());145				let call_context = CallContext {146					contract_address: *target,147					input: input.clone(),148					max_fee,149				};150				// Effects from EvmSponsorshipHandler are applied by pallet_evm::runner151				// TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`?152				let sponsor = frame_support::storage::with_transaction(|| {153					TransactionOutcome::Rollback(Ok::<_, DispatchError>(154						T::EvmSponsorshipHandler::get_sponsor(&who, &call_context),155					))156				})157				// FIXME: it may fail with DispatchError in case of depth limit158				.ok()??;159				Some(sponsor.as_sub().clone())160			}161			_ => None,162		}163	}164}165166/// Set transaction sponsor if available and enough balance.167pub struct TransactionValidity<T>(PhantomData<T>);168impl<T: Config> OnCheckEvmTransaction<T> for TransactionValidity<T> {169	fn on_check_evm_transaction(170		v: &mut CheckEvmTransaction,171		origin: &T::CrossAccountId,172	) -> Result<(), TransactionValidationError> {173		let who = &v.who;174		let max_fee_per_gas = v.transaction_fee_input()?.0;175		let gas_limit = v.transaction.gas_limit.low_u64();176		let reason = if let Some(to) = v.transaction.to {177			WithdrawReason::Call {178				target: to,179				input: v.transaction.input.clone(),180				max_fee_per_gas: Some(max_fee_per_gas),181				gas_limit,182				is_transactional: v.config.is_transactional,183				is_check: true,184			}185		} else {186			WithdrawReason::Create187		};188		let sponsor = get_sponsor::<T>(189			*origin.as_eth(),190			Some(max_fee_per_gas),191			gas_limit,192			&reason,193			v.config.is_transactional,194			true,195		)196		.as_ref()197		.map(pallet_evm::Pallet::<T>::account_basic_by_id)198		.map(|v| v.0);199		let fee = max_fee_per_gas.saturating_mul(v.transaction.gas_limit);200		if let Some(sponsor) = sponsor.as_ref() {201			if who.balance < v.transaction.value || sponsor.balance < fee {202				return Err(TransactionValidationError::BalanceTooLow);203			}204		} else {205			let total_payment = v.transaction.value.saturating_add(fee);206			if who.balance < total_payment {207				return Err(TransactionValidationError::BalanceTooLow);208			}209		}210211		let who = sponsor.unwrap_or_else(|| v.who.clone());212		v.who.balance = who.balance;213		Ok(())214	}215}216217/// Implements the transaction payment for a pallet implementing the `Currency`218/// trait (eg. the pallet_balances) using an unbalance handler (implementing219/// `OnUnbalanced`).220/// Similar to `CurrencyAdapter` of `pallet_transaction_payment`221pub struct WrappedEVMCurrencyAdapter<C, OU>(sp_std::marker::PhantomData<(C, OU)>);222impl<T, C, OU> OnChargeEVMTransaction<T> for WrappedEVMCurrencyAdapter<C, OU>223where224	T: Config,225	C: Currency<<T as frame_system::Config>::AccountId>,226	C::PositiveImbalance: Imbalance<227		<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,228		Opposite = C::NegativeImbalance,229	>,230	C::NegativeImbalance: Imbalance<231		<C as Currency<<T as frame_system::Config>::AccountId>>::Balance,232		Opposite = C::PositiveImbalance,233	>,234	OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,235	U256: UniqueSaturatedInto<<C as Currency<<T as frame_system::Config>::AccountId>>::Balance>,236{237	// Kept type as Option to satisfy bound of Default238	type LiquidityInfo = (Option<NegativeImbalanceOf<C, T>>, Option<T::CrossAccountId>);239240	fn withdraw_fee(241		who: &T::CrossAccountId,242		reason: WithdrawReason,243		fee: U256,244	) -> Result<Self::LiquidityInfo, pallet_evm::Error<T>> {245		let sponsor = match reason {246			WithdrawReason::Call {247				max_fee_per_gas,248				gas_limit,249				is_transactional,250				is_check,251				..252			} => get_sponsor::<T>(253				*who.as_eth(),254				max_fee_per_gas,255				gas_limit,256				&reason,257				is_transactional,258				is_check,259			),260			_ => None,261		};262263		let who = sponsor.as_ref().unwrap_or(who);264		<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::withdraw_fee(265			who, reason, fee,266		)267		.map(|li| (li, sponsor))268	}269270	fn correct_and_deposit_fee(271		who: &T::CrossAccountId,272		corrected_fee: U256,273		base_fee: U256,274		already_withdrawn: Self::LiquidityInfo,275	) -> Self::LiquidityInfo {276		let (already_withdrawn, sponsor) = already_withdrawn;277		let who = sponsor.as_ref().unwrap_or(who);278		(279			<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(280				who,281				corrected_fee,282				base_fee,283				already_withdrawn,284			),285			None286		)287	}288289	fn pay_priority_fee(tip: Self::LiquidityInfo) {290		<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::pay_priority_fee(291			tip.0,292		)293	}294}