diff --git a/runtime/src/fees.rs b/runtime/src/fees.rs new file mode 100644 index 00000000..793bf521 --- /dev/null +++ b/runtime/src/fees.rs @@ -0,0 +1,111 @@ +use frame_support::dispatch::DispatchInfo; +use frame_support::pallet_prelude::*; +use frame_support::traits::{Currency, Imbalance}; +use pallet_transaction_payment::{OnChargeTransaction, RuntimeDispatchInfo}; +use sp_runtime::traits::DispatchInfoOf; +use sp_runtime::SaturatedConversion; + +use crate::{Balance, RuntimeCall, Vec, DOLLARS}; + +pub(crate) fn special_fee_for_all_calls(calls: Vec) -> Option { + calls + .iter() + .filter_map(|call: &RuntimeCall| special_fee_for_call(call)) + .reduce(|a, b| a + b) +} + +pub(crate) fn special_fee_for_call(call: &RuntimeCall) -> Option { + // 20 SXT fee per table + let fee_per_table = DOLLARS * 20; + + match call.clone() { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) => { + special_fee_for_all_calls(calls) + } + RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { + special_fee_for_all_calls(calls) + } + RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) => { + special_fee_for_all_calls(calls) + } + RuntimeCall::Tables(pallet_tables::Call::create_tables { tables }) => { + Some(fee_per_table.saturating_mul(tables.len().saturated_into())) + } + RuntimeCall::Tables(pallet_tables::Call::create_tables_with_snapshot_and_commitment { + tables, + .. + }) => Some(fee_per_table.saturating_mul(tables.len().saturated_into())), + RuntimeCall::Tables(pallet_tables::Call::create_namespace { .. }) => Some(fee_per_table), + _ => None, + } +} + +/// A wrapper for custom gas fees that takes an Inner fallback +pub struct CustomGasFees(sp_std::marker::PhantomData<(C, Inner)>); + +impl OnChargeTransaction for CustomGasFees +where + T: frame_system::Config, + T: pallet_transaction_payment::Config, + C: Currency, + Fallback: + OnChargeTransaction, Balance = Balance>, +{ + type Balance = Balance; + type LiquidityInfo = Option; + + fn withdraw_fee( + who: &T::AccountId, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + fee: Self::Balance, + tip: Self::Balance, + ) -> Result { + // Calculate any special fees from table or namespace creation + if let Some(special_fee) = special_fee_for_call(call) { + // Attempt to perform the withdrawal, throwing an error if the account doesn't have the + // available funds + Ok(Some( + >::withdraw( + who, + special_fee, + frame_support::traits::WithdrawReasons::FEE, + frame_support::traits::ExistenceRequirement::KeepAlive, + ) + .map_err(|_| { + TransactionValidityError::Invalid( + sp_runtime::transaction_validity::InvalidTransaction::Payment, + ) + })?, + )) + } else { + // Calculate and withdraw the base fee for the transaction + Fallback::withdraw_fee(who, call, info, fee, tip) + } + } + + fn correct_and_deposit_fee( + who: &::AccountId, + dispatch_info: &sp_runtime::traits::DispatchInfoOf<::RuntimeCall>, + post_info: &sp_runtime::traits::PostDispatchInfoOf<::RuntimeCall>, + corrected_fee: Self::Balance, + tip: Self::Balance, + already_withdrawn: Self::LiquidityInfo, + ) -> Result<(), TransactionValidityError> { + // Force the user to always pay the estimate so that custom fees are respected + let final_fee = if let Some(ref paid) = already_withdrawn { + paid.peek() + } else { + corrected_fee + }; + + Fallback::correct_and_deposit_fee( + who, + dispatch_info, + post_info, + final_fee, + tip, + already_withdrawn, + ) + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a1f9979c..4a9e6e09 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -5,6 +5,10 @@ mod tests; +mod fees; + +use fees::*; + #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); @@ -13,8 +17,9 @@ use alloc::vec; use alloc::vec::Vec; use frame_election_provider_support::{generate_solution_type, onchain, SequentialPhragmen}; -use frame_support::dispatch::DispatchClass; +use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo}; use frame_support::genesis_builder_helper::{build_state, get_preset}; +use frame_support::pallet_prelude::TransactionValidityError; use frame_support::traits::VariantCountOf; pub use frame_support::traits::{ ConstBool, @@ -39,11 +44,12 @@ pub use frame_support::{construct_runtime, derive_impl, parameter_types, Storage pub use frame_system::Call as SystemCall; use frame_system::EnsureRoot; pub use pallet_balances::Call as BalancesCall; +use pallet_balances::NegativeImbalance; use pallet_election_provider_multi_phase::GeometricDepositBase; use pallet_grandpa::AuthorityId as GrandpaId; pub use pallet_im_online::sr25519::AuthorityId as ImOnlineId; pub use pallet_timestamp::Call as TimestampCall; -use pallet_transaction_payment::{CurrencyAdapter, Multiplier}; +use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, Multiplier, RuntimeDispatchInfo}; use proof_of_sql_commitment_map::generic_over_commitment::ConcreteType; use proof_of_sql_commitment_map::{AnyCommitmentScheme, PerCommitmentScheme, TableCommitmentBytes}; use sp_api::impl_runtime_apis; @@ -381,7 +387,7 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter; + type OnChargeTransaction = CustomGasFees>; type WeightToFee = ConstantMultiplier; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = (); @@ -980,6 +986,7 @@ pub type Executive = frame_executive::Executive< Migrations, >; +use pallet_transaction_payment::InclusionFee; #[cfg(feature = "runtime-benchmarks")] mod benches { frame_benchmarking::define_benchmarks!( @@ -1201,13 +1208,31 @@ impl_runtime_apis! { uxt: ::Extrinsic, len: u32, ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) + if let Some(special_fee) = special_fee_for_call(&uxt.function) { + let mut out = TransactionPayment::query_info(uxt, len); + out.partial_fee = special_fee; + out + } else { + TransactionPayment::query_info(uxt, len) + } } fn query_fee_details( uxt: ::Extrinsic, len: u32, ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_fee_details(uxt, len) + if let Some(special_fee) = special_fee_for_call(&uxt.function) { + FeeDetails { + inclusion_fee: Some( + InclusionFee { + base_fee: special_fee, + len_fee: 0, + adjusted_weight_fee: 0, + }), + tip: 0, + } + } else { + TransactionPayment::query_fee_details(uxt, len) + } } fn query_weight_to_fee(weight: Weight) -> Balance { TransactionPayment::weight_to_fee(weight) @@ -1224,13 +1249,37 @@ impl_runtime_apis! { call: RuntimeCall, len: u32, ) -> pallet_transaction_payment::RuntimeDispatchInfo { - TransactionPayment::query_call_info(call, len) + if let Some(special_fee) = special_fee_for_call(&call) { + let dispatch_info = ::get_dispatch_info(&call); + let DispatchInfo { weight, class, .. } = dispatch_info; + + RuntimeDispatchInfo { + weight, + class, + partial_fee: special_fee, + } + + } else { + TransactionPayment::query_call_info(call, len) + } } fn query_call_fee_details( call: RuntimeCall, len: u32, ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_call_fee_details(call, len) + if let Some(special_fee) = special_fee_for_call(&call) { + FeeDetails { + inclusion_fee: Some( + InclusionFee { + base_fee: special_fee, + len_fee: 0, + adjusted_weight_fee: 0, + }), + tip: 0, + } + } else { + TransactionPayment::query_call_fee_details(call, len) + } } fn query_weight_to_fee(weight: Weight) -> Balance { TransactionPayment::weight_to_fee(weight)