difftreelog
feat use new ethereum sponsoring interface
in: master
2 files changed
pallets/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,
+ )
+ }
+}
runtime/common/config/ethereum.rsdiffbeforeafterboth1use 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}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}