git.delta.rocks / unique-network / refs/commits / 71150e7c9b75

difftreelog

feat use new ethereum sponsoring interface

Yaroslav Bolyukin2023-10-02parent: #6ab76d3.patch.diff
in: master

2 files changed

modifiedpallets/evm-transaction-payment/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-transaction-payment/src/lib.rs
+++ b/pallets/evm-transaction-payment/src/lib.rs
@@ -60,29 +60,62 @@
 	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(
-		origin: H160,
-		max_fee: U256,
-		reason: &WithdrawReason,
-	) -> Option<T::CrossAccountId> {
-		match reason {
-			WithdrawReason::Call { target, input } => {
-				let origin_sub = T::CrossAccountId::from_eth(origin);
-				let call_context = CallContext {
-					contract_address: *target,
-					input: input.clone(),
-					max_fee,
-				};
-				T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context)
-			}
-			_ => None,
+fn who_pays_fee<T: Config>(
+	origin: H160,
+	max_fee: U256,
+	reason: &WithdrawReason,
+) -> Option<T::CrossAccountId> {
+	match reason {
+		WithdrawReason::Call { target, input, .. } => {
+			let origin_sub = T::CrossAccountId::from_eth(origin);
+			let call_context = CallContext {
+				contract_address: *target,
+				input: input.clone(),
+				max_fee,
+			};
+			T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context)
 		}
+		_ => None,
 	}
 }
 
+fn get_sponsor<T: Config>(
+	source: H160,
+	max_fee_per_gas: Option<U256>,
+	gas_limit: u64,
+	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.into());
+
+	// #[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)
 pub struct BridgeSponsorshipHandler<T>(PhantomData<T>);
 impl<T, C> SponsorshipHandler<T::AccountId, C> for BridgeSponsorshipHandler<T>
@@ -127,3 +160,135 @@
 		}
 	}
 }
+
+/// Set transaction sponsor if available and enough balance.
+pub struct TransactionValidity<T>(PhantomData<T>);
+impl<T: Config> OnCheckEvmTransaction<T> for TransactionValidity<T> {
+	fn on_check_evm_transaction(
+		v: &mut CheckEvmTransaction,
+		origin: &T::CrossAccountId,
+	) -> Result<(), TransactionValidationError> {
+		let who = &v.who;
+		let max_fee_per_gas = Some(v.transaction_fee_input()?.0);
+		let gas_limit = v.transaction.gas_limit.low_u64();
+		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(TransactionValidationError::BalanceTooLow.into());
+			}
+		} else {
+			let total_payment = v.transaction.value.saturating_add(fee);
+			if who.balance < total_payment {
+				return Err(TransactionValidationError::BalanceTooLow.into());
+			}
+		}
+
+		let who = sponsor.unwrap_or_else(|| v.who.clone());
+		v.who.balance = who.balance;
+		Ok(())
+	}
+}
+
+/// 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,
+			),
+			None
+		)
+	}
+
+	fn pay_priority_fee(tip: Self::LiquidityInfo) {
+		<pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::pay_priority_fee(
+			tip.0,
+		)
+	}
+}
modifiedruntime/common/config/ethereum.rsdiffbeforeafterboth
before · runtime/common/config/ethereum.rs
1use frame_support::{2	parameter_types,3	traits::FindAuthor,4	weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},5	ConsensusEngineId,6};7use pallet_ethereum::PostLogContent;8use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping};9use sp_core::{H160, U256};10use sp_runtime::{traits::ConstU32, Perbill, RuntimeAppPublic};11use up_common::constants::*;1213use crate::{14	runtime_common::{15		config::sponsoring::DefaultSponsoringRateLimit,16		dispatch::CollectionDispatchT,17		ethereum::{precompiles::UniquePrecompiles, sponsoring::EvmSponsorshipHandler},18		DealWithFees,19	},20	Aura, Balances, ChainId, Runtime, RuntimeEvent,21};2223pub type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;2425// Assuming PoV size per read is 96 bytes: 16 for twox128(Evm), 16 for twox128(Storage), 32 for storage key, and 32 for storage value26const EVM_SLOAD_PROOF_SIZE: u64 = 96;2728// ~~Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case~~29// ~~(contract, which only writes a lot of data),~~30// ~~approximating on top of our real store write weight~~31//32// The above approach is very wrong, and the reason is described there:33// https://forum.polkadot.network/t/frontier-support-for-evm-weight-v2/2470/5#problem-234parameter_types! {35	pub const ReadsPerSecond: u64 = WEIGHT_REF_TIME_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().read;36	pub const GasPerSecond: u64 = ReadsPerSecond::get() * 2100;37	pub const WeightTimePerGas: u64 = WEIGHT_REF_TIME_PER_SECOND / GasPerSecond::get();3839	pub const BytesReadPerSecond: u64 = ReadsPerSecond::get() * EVM_SLOAD_PROOF_SIZE;40	pub const ProofSizePerGas: u64 = 0; //WEIGHT_REF_TIME_PER_SECOND / GasPerSecond::get();4142	pub const WeightPerGas: Weight = Weight::from_parts(WeightTimePerGas::get(), ProofSizePerGas::get());43}4445/// Limiting EVM execution to 50% of block for substrate users and management tasks46/// EVM transaction consumes more weight than substrate's, so we can't rely on them being47/// scheduled fairly48const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);49parameter_types! {50	pub BlockGasLimit: U256 = U256::from((NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightTimePerGas::get()).ref_time());51	pub PrecompilesValue: UniquePrecompiles<Runtime> = UniquePrecompiles::<_>::new();52}5354pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);55impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {56	fn find_author<'a, I>(digests: I) -> Option<H160>57	where58		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,59	{60		if let Some(author_index) = F::find_author(digests) {61			let authority_id = Aura::authorities()[author_index as usize].clone();62			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));63		}64		None65	}66}6768impl pallet_evm::Config for Runtime {69	type CrossAccountId = CrossAccountId;70	type AddressMapping = HashedAddressMapping<Self::Hashing>;71	type BackwardsAddressMapping = HashedAddressMapping<Self::Hashing>;72	type BlockGasLimit = BlockGasLimit;73	type FeeCalculator = pallet_configuration::FeeCalculator<Self>;74	type GasWeightMapping = pallet_evm::FixedGasWeightMapping<Self>;75	type WeightPerGas = WeightPerGas;76	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;77	type CallOrigin = EnsureAddressTruncated<Self>;78	type WithdrawOrigin = EnsureAddressTruncated<Self>;79	type PrecompilesType = UniquePrecompiles<Self>;80	type PrecompilesValue = PrecompilesValue;81	type Currency = Balances;82	type RuntimeEvent = RuntimeEvent;83	type OnMethodCall = (84		pallet_evm_migration::OnMethodCall<Self>,85		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,86		CollectionDispatchT<Self>,87		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,88	);89	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;90	type ChainId = ChainId;91	type Runner = pallet_evm::runner::stack::Runner<Self>;92	type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;93	type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;94	type FindAuthor = EthereumFindAuthor<Aura>;95	type Timestamp = crate::Timestamp;96	type WeightInfo = pallet_evm::weights::SubstrateWeight<Self>;97	type GasLimitPovSizeRatio = ProofSizePerGas;98}99100impl pallet_evm_migration::Config for Runtime {101	type RuntimeEvent = RuntimeEvent;102	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;103}104105parameter_types! {106	pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes;107}108109impl pallet_ethereum::Config for Runtime {110	type RuntimeEvent = RuntimeEvent;111	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;112	type PostLogContent = PostBlockAndTxnHashes;113	// Space for revert reason. Ethereum transactions are not cheap, and overall size is much less114	// than the substrate tx size, so we can afford this115	type ExtraDataLength = ConstU32<32>;116}117118parameter_types! {119	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049120	pub const HelpersContractAddress: H160 = H160([121		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,122	]);123124	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f125	pub const EvmCollectionHelpersAddress: H160 = H160([126		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,127	]);128}129130impl pallet_evm_contract_helpers::Config for Runtime {131	type RuntimeEvent = RuntimeEvent;132	type ContractAddress = HelpersContractAddress;133	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;134}135136impl pallet_evm_coder_substrate::Config for Runtime {}137138impl pallet_evm_transaction_payment::Config for Runtime {139	type EvmSponsorshipHandler = EvmSponsorshipHandler;140}
after · runtime/common/config/ethereum.rs
1use frame_support::{2	parameter_types,3	traits::FindAuthor,4	weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},5	ConsensusEngineId,6};7use pallet_ethereum::PostLogContent;8use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping};9use sp_core::{H160, U256};10use sp_runtime::{traits::ConstU32, Perbill, RuntimeAppPublic};11use up_common::constants::*;1213use crate::{14	runtime_common::{15		config::sponsoring::DefaultSponsoringRateLimit,16		dispatch::CollectionDispatchT,17		ethereum::{precompiles::UniquePrecompiles, sponsoring::EvmSponsorshipHandler},18		DealWithFees,19	},20	Aura, Balances, ChainId, Runtime, RuntimeEvent,21};2223pub type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;2425// Assuming PoV size per read is 96 bytes: 16 for twox128(Evm), 16 for twox128(Storage), 32 for storage key, and 32 for storage value26const EVM_SLOAD_PROOF_SIZE: u64 = 96;2728// ~~Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case~~29// ~~(contract, which only writes a lot of data),~~30// ~~approximating on top of our real store write weight~~31//32// The above approach is very wrong, and the reason is described there:33// https://forum.polkadot.network/t/frontier-support-for-evm-weight-v2/2470/5#problem-234parameter_types! {35	pub const ReadsPerSecond: u64 = WEIGHT_REF_TIME_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().read;36	pub const GasPerSecond: u64 = ReadsPerSecond::get() * 2100;37	pub const WeightTimePerGas: u64 = WEIGHT_REF_TIME_PER_SECOND / GasPerSecond::get();3839	pub const BytesReadPerSecond: u64 = ReadsPerSecond::get() * EVM_SLOAD_PROOF_SIZE;40	pub const ProofSizePerGas: u64 = 0; //WEIGHT_REF_TIME_PER_SECOND / GasPerSecond::get();4142	pub const WeightPerGas: Weight = Weight::from_parts(WeightTimePerGas::get(), ProofSizePerGas::get());43}4445/// Limiting EVM execution to 50% of block for substrate users and management tasks46/// EVM transaction consumes more weight than substrate's, so we can't rely on them being47/// scheduled fairly48const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);49parameter_types! {50	pub BlockGasLimit: U256 = U256::from((NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightTimePerGas::get()).ref_time());51	pub PrecompilesValue: UniquePrecompiles<Runtime> = UniquePrecompiles::<_>::new();52}5354pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);55impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {56	fn find_author<'a, I>(digests: I) -> Option<H160>57	where58		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,59	{60		if let Some(author_index) = F::find_author(digests) {61			let authority_id = Aura::authorities()[author_index as usize].clone();62			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));63		}64		None65	}66}6768impl pallet_evm::Config for Runtime {69	type CrossAccountId = CrossAccountId;70	type AddressMapping = HashedAddressMapping<Self::Hashing>;71	type BackwardsAddressMapping = HashedAddressMapping<Self::Hashing>;72	type BlockGasLimit = BlockGasLimit;73	type FeeCalculator = pallet_configuration::FeeCalculator<Self>;74	type GasWeightMapping = pallet_evm::FixedGasWeightMapping<Self>;75	type WeightPerGas = WeightPerGas;76	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;77	type CallOrigin = EnsureAddressTruncated<Self>;78	type WithdrawOrigin = EnsureAddressTruncated<Self>;79	type PrecompilesType = UniquePrecompiles<Self>;80	type PrecompilesValue = PrecompilesValue;81	type Currency = Balances;82	type RuntimeEvent = RuntimeEvent;83	type OnMethodCall = (84		pallet_evm_migration::OnMethodCall<Self>,85		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,86		CollectionDispatchT<Self>,87		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,88	);89	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;90	type ChainId = ChainId;91	type Runner = pallet_evm::runner::stack::Runner<Self>;92	type OnChargeTransaction =93		pallet_evm_transaction_payment::WrappedEVMCurrencyAdapter<Balances, DealWithFees>;94	type FindAuthor = EthereumFindAuthor<Aura>;95	type Timestamp = crate::Timestamp;96	type WeightInfo = pallet_evm::weights::SubstrateWeight<Self>;97	type GasLimitPovSizeRatio = ProofSizePerGas;98	type OnCheckEvmTransaction = pallet_evm_transaction_payment::TransactionValidity<Self>;99}100101impl pallet_evm_migration::Config for Runtime {102	type RuntimeEvent = RuntimeEvent;103	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;104}105106parameter_types! {107	pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes;108}109110impl pallet_ethereum::Config for Runtime {111	type RuntimeEvent = RuntimeEvent;112	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;113	type PostLogContent = PostBlockAndTxnHashes;114	// Space for revert reason. Ethereum transactions are not cheap, and overall size is much less115	// than the substrate tx size, so we can afford this116	type ExtraDataLength = ConstU32<32>;117}118119parameter_types! {120	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049121	pub const HelpersContractAddress: H160 = H160([122		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,123	]);124125	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f126	pub const EvmCollectionHelpersAddress: H160 = H160([127		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,128	]);129}130131impl pallet_evm_contract_helpers::Config for Runtime {132	type RuntimeEvent = RuntimeEvent;133	type ContractAddress = HelpersContractAddress;134	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;135}136137impl pallet_evm_coder_substrate::Config for Runtime {}138139impl pallet_evm_transaction_payment::Config for Runtime {140	type EvmSponsorshipHandler = EvmSponsorshipHandler;141}