From 89e048fff532072cb592ce836e3abe8fe74dba48 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 8 May 2026 16:00:42 -0400 Subject: [PATCH 1/3] Implement OnTransactionPayment for EVM pallet --- Cargo.lock | 1 + pallets/transaction-fee/Cargo.toml | 4 ++ pallets/transaction-fee/src/lib.rs | 81 ++++++++++++++++++++++++ runtime/src/lib.rs | 6 +- runtime/tests/evm_transaction_fee.rs | 94 ++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 runtime/tests/evm_transaction_fee.rs diff --git a/Cargo.lock b/Cargo.lock index c2650935ed..32d4c7655d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18339,6 +18339,7 @@ dependencies = [ "pallet-balances", "pallet-crowdloan", "pallet-drand", + "pallet-evm", "pallet-evm-chain-id", "pallet-grandpa", "pallet-preimage", diff --git a/pallets/transaction-fee/Cargo.toml b/pallets/transaction-fee/Cargo.toml index 272faf3198..0c6058814e 100644 --- a/pallets/transaction-fee/Cargo.toml +++ b/pallets/transaction-fee/Cargo.toml @@ -10,10 +10,12 @@ frame-system.workspace = true log.workspace = true pallet-balances = { workspace = true, default-features = false } pallet-alpha-assets = { workspace = true, default-features = false } +pallet-evm.workspace = true pallet-subtensor.workspace = true pallet-subtensor-swap.workspace = true pallet-transaction-payment.workspace = true smallvec.workspace = true +sp-core.workspace = true sp-runtime.workspace = true sp-std.workspace = true substrate-fixed.workspace = true @@ -52,6 +54,7 @@ std = [ "pallet-balances/std", "pallet-crowdloan/std", "pallet-drand/std", + "pallet-evm/std", "pallet-evm-chain-id/std", "pallet-grandpa/std", "pallet-preimage/std", @@ -60,6 +63,7 @@ std = [ "pallet-subtensor-swap/std", "pallet-transaction-payment/std", "scale-info/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", "sp-consensus-aura/std", diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index 72b27cdcfb..0bc027ee49 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -13,6 +13,9 @@ use frame_support::{ }, weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial}, }; +use pallet_evm::{ + AddressMapping, BalanceConverter, Config as EvmConfig, EvmBalance, OnChargeEVMTransaction, +}; // Runtime use sp_runtime::{ @@ -29,6 +32,7 @@ use subtensor_swap_interface::SwapHandler; // Misc use core::marker::PhantomData; use smallvec::smallvec; +use sp_core::H160; use sp_runtime::traits::SaturatedConversion; use sp_std::vec::Vec; use subtensor_runtime_common::{AlphaBalance, AuthorshipInfo, NetUid, TaoBalance}; @@ -229,6 +233,8 @@ pub enum WithdrawnFee>> { /// pub struct SubtensorTxFeeHandler(PhantomData<(F, OU)>); +pub struct SubtensorEvmFeeHandler(PhantomData<(F, OU)>); + /// This implementation contains the list of calls that require paying transaction /// fees in Alpha impl SubtensorTxFeeHandler { @@ -439,3 +445,78 @@ where F::minimum_balance() } } + +impl OnChargeEVMTransaction for SubtensorEvmFeeHandler +where + T: EvmConfig + pallet_subtensor::Config, + F: Balanced, + OU: OnUnbalanced>, + T::AddressMapping: AddressMapping, + >::Balance: From + Into, +{ + type LiquidityInfo = Option>; + + fn withdraw_fee( + who: &H160, + fee: EvmBalance, + ) -> Result> { + if fee.into_u256().is_zero() { + return Ok(None); + } + + let account_id = >::into_account_id(*who); + let fee_sub = T::BalanceConverter::into_substrate_balance(fee) + .ok_or(pallet_evm::Error::::FeeOverflow)?; + + let imbalance = F::withdraw( + &account_id, + TaoBalance::from(fee_sub.into_u64_saturating()).into(), + Precision::Exact, + frame_support::traits::tokens::Preservation::Preserve, + frame_support::traits::tokens::Fortitude::Polite, + ) + .map_err(|_| pallet_evm::Error::::BalanceLow)?; + + Ok(Some(imbalance)) + } + + fn correct_and_deposit_fee( + who: &H160, + corrected_fee: EvmBalance, + base_fee: EvmBalance, + already_withdrawn: Self::LiquidityInfo, + ) -> Self::LiquidityInfo { + if let Some(paid) = already_withdrawn { + let account_id = + >::into_account_id(*who); + let corrected_fee_sub = T::BalanceConverter::into_substrate_balance(corrected_fee) + .unwrap_or_else(|| 0u64.into()); + let refund_amount = paid + .peek() + .saturating_sub(TaoBalance::from(corrected_fee_sub.into_u64_saturating()).into()); + let refund_imbalance = F::deposit(&account_id, refund_amount, Precision::BestEffort) + .unwrap_or_else(|_| Debt::::zero()); + let adjusted_paid = paid + .offset(refund_imbalance) + .same() + .unwrap_or_else(|_| Credit::::zero()); + let base_fee_sub = T::BalanceConverter::into_substrate_balance(base_fee) + .unwrap_or_else(|| 0u64.into()); + let (base_fee_credit, tip) = + adjusted_paid.split(TaoBalance::from(base_fee_sub.into_u64_saturating()).into()); + OU::on_unbalanced(base_fee_credit); + return Some(tip); + } + + None + } + + fn pay_priority_fee(tip: Self::LiquidityInfo) { + if let Some(tip) = tip { + let author = >::into_account_id( + pallet_evm::Pallet::::find_author(), + ); + let _ = F::resolve(&author, tip); + } + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 00d3839fa7..17e05c29fa 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -101,7 +101,9 @@ use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; -use subtensor_transaction_fee::{SubtensorTxFeeHandler, TransactionFeeHandler}; +use subtensor_transaction_fee::{ + SubtensorEvmFeeHandler, SubtensorTxFeeHandler, TransactionFeeHandler, +}; use core::marker::PhantomData; @@ -1400,7 +1402,7 @@ impl pallet_evm::Config for Runtime { type ChainId = ConfigurableChainId; type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = (); + type OnChargeTransaction = SubtensorEvmFeeHandler>; type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; diff --git a/runtime/tests/evm_transaction_fee.rs b/runtime/tests/evm_transaction_fee.rs new file mode 100644 index 0000000000..19cab4c77d --- /dev/null +++ b/runtime/tests/evm_transaction_fee.rs @@ -0,0 +1,94 @@ +#![allow(clippy::expect_used, clippy::unwrap_used)] + +use codec::Encode; +use frame_support::traits::fungible::Inspect; +use node_subtensor_runtime::{Aura, Balances, BuildStorage, Runtime, RuntimeGenesisConfig}; +use pallet_evm::{AddressMapping, EvmBalance, OnChargeEVMTransaction}; +use sp_consensus_aura::{AURA_ENGINE_ID, sr25519::AuthorityId as AuraId}; +use sp_core::H160; +use sp_core::sr25519; +use subtensor_runtime_common::{AuthorshipInfo, TaoBalance}; + +fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { + ..Default::default() + } + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1)); + ext +} + +fn add_balance_to_coldkey_account(coldkey: &sp_core::crypto::AccountId32, tao: TaoBalance) { + let credit = pallet_subtensor::Pallet::::mint_tao(tao); + let _ = pallet_subtensor::Pallet::::spend_tao(coldkey, credit, tao).unwrap(); +} + +fn initialize_block_with_aura_authority(authority: AuraId, slot: u64) { + Aura::change_authorities(vec![authority].try_into().unwrap()); + let digest = sp_runtime::Digest { + logs: vec![sp_runtime::DigestItem::PreRuntime( + AURA_ENGINE_ID, + slot.encode(), + )], + }; + frame_system::Pallet::::initialize(&1u32.into(), &Default::default(), &digest); +} + +#[test] +fn evm_fee_refund_does_not_change_total_issuance() { + new_test_ext().execute_with(|| { + initialize_block_with_aura_authority(AuraId::from(sr25519::Public::from_raw([1u8; 32])), 0); + + let evm_addr = H160::from_low_u64_be(7); + let account_id = ::AddressMapping::into_account_id(evm_addr); + let substrate_author = >::author() + .expect("aura digest should provide a substrate block author"); + let evm_author = + ::AddressMapping::into_account_id(pallet_evm::Pallet::< + Runtime, + >::find_author( + )); + + add_balance_to_coldkey_account(&account_id, 1_000_000_000u64.into()); + add_balance_to_coldkey_account(&substrate_author, 1_000_000_000u64.into()); + add_balance_to_coldkey_account(&evm_author, 1_000_000_000u64.into()); + + let balances_issuance_before = Balances::total_issuance(); + let subtensor_issuance_before = pallet_subtensor::Pallet::::get_total_issuance(); + let balance_before = Balances::total_balance(&account_id); + + assert_eq!(balances_issuance_before, subtensor_issuance_before); + + let withdrawn = + <::OnChargeTransaction as OnChargeEVMTransaction< + Runtime, + >>::withdraw_fee(&evm_addr, EvmBalance::from(10_000_000_000u128)) + .unwrap(); + + let tip = + <::OnChargeTransaction as OnChargeEVMTransaction< + Runtime, + >>::correct_and_deposit_fee( + &evm_addr, + EvmBalance::from(5_000_000_000u128), + EvmBalance::from(3_000_000_000u128), + withdrawn, + ); + + <::OnChargeTransaction as OnChargeEVMTransaction< + Runtime, + >>::pay_priority_fee(tip); + + assert_eq!( + Balances::total_issuance(), + pallet_subtensor::Pallet::::get_total_issuance() + ); + assert_eq!( + Balances::total_balance(&account_id), + balance_before - TaoBalance::from(5) + ); + }); +} From 1cdcf4a90cdcc86da52d99cba64681573f8ff981 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 8 May 2026 16:18:14 -0400 Subject: [PATCH 2/3] zepter --- pallets/transaction-fee/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/transaction-fee/Cargo.toml b/pallets/transaction-fee/Cargo.toml index 0c6058814e..9e19bf2758 100644 --- a/pallets/transaction-fee/Cargo.toml +++ b/pallets/transaction-fee/Cargo.toml @@ -84,6 +84,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-crowdloan/runtime-benchmarks", "pallet-drand/runtime-benchmarks", + "pallet-evm/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", From 0b22ece9e3b2119f3372c3520f895d536e6024fd Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 8 May 2026 16:18:58 -0400 Subject: [PATCH 3/3] spec bump --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 17e05c29fa..84e7964f86 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -274,7 +274,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 406, + spec_version: 407, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,