use crate::{
BalanceOf, CallOf, Config, DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Error,
LOG_TARGET, PostDispatchInfo,
evm::{
OnChargeTransactionBalanceOf,
runtime::{EthExtra, SetWeightLimit},
},
};
use codec::Encode;
use core::marker::PhantomData;
use frame_support::{
dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo},
pallet_prelude::Weight,
traits::{Get, SuppressedDrop, fungible::Credit, tokens::Balance},
weights::WeightToFee,
};
use frame_system::Config as SysConfig;
use num_traits::{One, Zero};
use pallet_transaction_payment::{
Config as TxConfig, MultiplierUpdate, NextFeeMultiplier, Pallet as TxPallet, TxCreditHold,
};
use sp_arithmetic::{FixedPointOperand, SignedRounding};
use sp_runtime::{
FixedPointNumber, FixedU128, SaturatedConversion, Saturating,
generic::UncheckedExtrinsic,
traits::{
Block as BlockT, Dispatchable, ExtensionPostDispatchWeightHandler, TransactionExtension,
},
};
type CreditOf<T> = Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
pub struct BlockRatioFee<const P: u128, const Q: u128, T, B>(PhantomData<(T, B)>);
pub struct Info<Address, Signature, Extra>(PhantomData<(Address, Signature, Extra)>);
pub trait InfoT<T: Config>: seal::Sealed {
fn integrity_test() {}
fn next_fee_multiplier() -> FixedU128 {
FixedU128::from_rational(1, 1)
}
fn next_fee_multiplier_reciprocal() -> FixedU128 {
Self::next_fee_multiplier()
.reciprocal()
.expect("The minimum multiplier is not 0. We check that in `integrity_test`; qed")
}
fn tx_fee(_len: u32, _call: &CallOf<T>) -> BalanceOf<T> {
Zero::zero()
}
fn tx_fee_from_weight(_encoded_len: u32, _weight: &Weight) -> BalanceOf<T> {
Zero::zero()
}
fn fixed_fee(_encoded_len: u32) -> BalanceOf<T> {
Zero::zero()
}
fn ensure_not_overdrawn(
_fee: BalanceOf<T>,
result: DispatchResultWithPostInfo,
) -> DispatchResultWithPostInfo {
result
}
fn dispatch_info(_call: &CallOf<T>) -> DispatchInfo {
Default::default()
}
fn base_dispatch_info(_call: &mut CallOf<T>) -> DispatchInfo {
Default::default()
}
fn encoded_len(_eth_transact_call: CallOf<T>) -> u32 {
0
}
fn weight_to_fee(_weight: &Weight) -> BalanceOf<T> {
Zero::zero()
}
fn weight_to_fee_average(_weight: &Weight) -> BalanceOf<T> {
Zero::zero()
}
fn fee_to_weight(_fee: BalanceOf<T>) -> Weight {
Zero::zero()
}
fn length_to_fee(_len: u32) -> BalanceOf<T> {
Zero::zero()
}
fn deposit_txfee(_credit: CreditOf<T>) {}
fn withdraw_txfee(_amount: BalanceOf<T>) -> Option<CreditOf<T>> {
Default::default()
}
fn remaining_txfee() -> BalanceOf<T> {
Default::default()
}
fn compute_actual_fee(
_encoded_len: u32,
_info: &DispatchInfo,
_result: &DispatchResultWithPostInfo,
) -> BalanceOf<T> {
Default::default()
}
}
impl<const P: u128, const Q: u128, T: SysConfig, B> BlockRatioFee<P, Q, T, B> {
const REF_TIME_TO_FEE: FixedU128 = {
assert!(P > 0 && Q > 0);
FixedU128::from_rational(P, Q)
};
fn proof_size_to_fee() -> FixedU128 {
let max_weight = <T as SysConfig>::BlockWeights::get().max_block;
let ratio =
FixedU128::from_rational(max_weight.ref_time().into(), max_weight.proof_size().into());
Self::REF_TIME_TO_FEE.saturating_mul(ratio)
}
}
impl<const P: u128, const Q: u128, T, B> WeightToFee for BlockRatioFee<P, Q, T, B>
where
T: SysConfig,
B: Balance,
{
type Balance = B;
fn weight_to_fee(weight: &Weight) -> Self::Balance {
let ref_time_fee = Self::REF_TIME_TO_FEE
.saturating_mul_int(Self::Balance::saturated_from(weight.ref_time()));
let proof_size_fee = Self::proof_size_to_fee()
.saturating_mul_int(Self::Balance::saturated_from(weight.proof_size()));
ref_time_fee.max(proof_size_fee)
}
}
impl<const P: u128, const Q: u128, Address, Signature, E: EthExtra> InfoT<E::Config>
for Info<Address, Signature, E>
where
E::Config: TxConfig<WeightToFee = BlockRatioFee<P, Q, E::Config, BalanceOf<E::Config>>>,
BalanceOf<E::Config>: From<OnChargeTransactionBalanceOf<E::Config>>,
<E::Config as frame_system::Config>::RuntimeCall:
Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
CallOf<E::Config>: SetWeightLimit,
<<E::Config as SysConfig>::Block as BlockT>::Extrinsic: From<
UncheckedExtrinsic<
Address,
CallOf<E::Config>,
Signature,
E::ExtensionV0,
E::ExtensionOtherVersions,
>,
>,
<<E::Config as TxConfig>::OnChargeTransaction as TxCreditHold<E::Config>>::Credit:
SuppressedDrop<Inner = CreditOf<E::Config>>,
{
fn integrity_test() {
let min_multiplier = <E::Config as TxConfig>::FeeMultiplierUpdate::min();
assert!(!min_multiplier.is_zero(), "The multiplier is never allowed to be zero.");
assert!(
min_multiplier.saturating_mul_int(<E::Config as Config>::NativeToEthRatio::get()) > 0,
"The gas price needs to be greater zero."
);
assert!(
!<E::Config as TxConfig>::WeightToFee::REF_TIME_TO_FEE.is_zero(),
"ref_time to fee is not allowed to be zero."
);
assert!(
!<E::Config as TxConfig>::WeightToFee::proof_size_to_fee().is_zero(),
"proof_size to fee is not allowed to be zero."
);
}
fn next_fee_multiplier() -> FixedU128 {
<NextFeeMultiplier<E::Config>>::get()
}
fn tx_fee(len: u32, call: &CallOf<E::Config>) -> BalanceOf<E::Config> {
let dispatch_info = Self::dispatch_info(call);
TxPallet::<E::Config>::compute_fee(len, &dispatch_info, 0u32.into()).into()
}
fn tx_fee_from_weight(encoded_len: u32, weight: &Weight) -> BalanceOf<E::Config> {
let fixed_fee = Self::fixed_fee(encoded_len);
let weight_fee =
Self::next_fee_multiplier().saturating_mul_int(Self::weight_to_fee(weight));
fixed_fee.saturating_add(weight_fee)
}
fn fixed_fee(encoded_len: u32) -> BalanceOf<E::Config> {
Self::weight_to_fee(
&<E::Config as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.base_extrinsic,
)
.saturating_add(Self::length_to_fee(encoded_len))
}
fn ensure_not_overdrawn(
fee: BalanceOf<E::Config>,
result: DispatchResultWithPostInfo,
) -> DispatchResultWithPostInfo {
let Ok(post_info) = result else {
return result;
};
let available = Self::remaining_txfee();
if fee > available {
log::debug!(target: LOG_TARGET, "Drew too much from the txhold. \
fee={fee:?} \
available={available:?} \
overdrawn_by={:?}",
fee.saturating_sub(available),
);
Err(DispatchErrorWithPostInfo {
post_info,
error: <Error<E::Config>>::TxFeeOverdraw.into(),
})
} else {
log::trace!(target: LOG_TARGET, "Enough left in the txhold. \
fee={fee:?} \
available={available:?} \
refund={:?}",
available.saturating_sub(fee),
);
result
}
}
fn dispatch_info(call: &CallOf<E::Config>) -> DispatchInfo {
let mut dispatch_info = call.get_dispatch_info();
dispatch_info.extension_weight =
E::get_eth_extension(0u32.into(), 0u32.into()).weight(call);
dispatch_info
}
fn base_dispatch_info(call: &mut CallOf<E::Config>) -> DispatchInfo {
let pre_weight = call.set_weight_limit(Zero::zero());
let info = Self::dispatch_info(call);
call.set_weight_limit(pre_weight);
info
}
fn encoded_len(eth_transact_call: CallOf<E::Config>) -> u32 {
let uxt: <<E::Config as SysConfig>::Block as BlockT>::Extrinsic =
UncheckedExtrinsic::new_bare(eth_transact_call).into();
uxt.encoded_size() as u32
}
fn weight_to_fee(weight: &Weight) -> BalanceOf<E::Config> {
<E::Config as TxConfig>::WeightToFee::weight_to_fee(weight)
}
fn weight_to_fee_average(weight: &Weight) -> BalanceOf<E::Config> {
let ref_time_part = <E::Config as TxConfig>::WeightToFee::REF_TIME_TO_FEE
.saturating_mul_int(weight.ref_time());
let proof_size_part = <E::Config as TxConfig>::WeightToFee::proof_size_to_fee()
.saturating_mul_int(weight.proof_size());
((ref_time_part / 2).saturating_add(proof_size_part / 2)).saturated_into()
}
fn fee_to_weight(fee: BalanceOf<E::Config>) -> Weight {
let ref_time_to_fee = <E::Config as TxConfig>::WeightToFee::REF_TIME_TO_FEE;
let proof_size_to_fee = <E::Config as TxConfig>::WeightToFee::proof_size_to_fee();
let (ref_time, proof_size) =
compute_max_integer_pair_quotient((ref_time_to_fee, proof_size_to_fee), fee);
Weight::from_parts(ref_time.saturated_into(), proof_size.saturated_into())
}
fn length_to_fee(len: u32) -> BalanceOf<E::Config> {
TxPallet::<E::Config>::length_to_fee(len).into()
}
fn deposit_txfee(credit: CreditOf<E::Config>) {
TxPallet::<E::Config>::deposit_txfee(credit)
}
fn withdraw_txfee(amount: BalanceOf<E::Config>) -> Option<CreditOf<E::Config>> {
TxPallet::<E::Config>::withdraw_txfee(amount)
}
fn remaining_txfee() -> BalanceOf<E::Config> {
TxPallet::<E::Config>::remaining_txfee()
}
fn compute_actual_fee(
encoded_len: u32,
info: &DispatchInfo,
result: &DispatchResultWithPostInfo,
) -> BalanceOf<E::Config> {
let mut post_info = *match result {
Ok(post_info) => post_info,
Err(err) => &err.post_info,
};
post_info.set_extension_weight(info);
<TxPallet<E::Config>>::compute_actual_fee(encoded_len, info, &post_info, Zero::zero())
.into()
}
}
impl<T: Config> InfoT<T> for () {}
mod seal {
pub trait Sealed {}
impl<Address, Signature, E: super::EthExtra> Sealed for super::Info<Address, Signature, E> {}
impl Sealed for () {}
}
pub fn compute_max_integer_quotient<F: FixedPointOperand + One>(
multiplier: FixedU128,
product: F,
) -> F {
let one = F::one();
let product_plus_one = FixedU128::from_inner(product.saturating_add(one).saturated_into());
product_plus_one
.checked_rounding_div(multiplier, SignedRounding::Major)
.map(|f| f.into_inner().saturated_into::<F>().saturating_sub(one))
.unwrap_or(F::max_value())
}
pub fn compute_max_integer_pair_quotient<F: FixedPointOperand + One>(
multiplier: (FixedU128, FixedU128),
product: F,
) -> (F, F) {
let one = F::one();
let product_plus_one = FixedU128::from_inner(product.saturating_add(one).saturated_into());
let result1 = product_plus_one
.checked_rounding_div(multiplier.0, SignedRounding::Major)
.map(|f| f.into_inner().saturated_into::<F>().saturating_sub(one))
.unwrap_or(F::max_value());
let result2 = product_plus_one
.checked_rounding_div(multiplier.1, SignedRounding::Major)
.map(|f| f.into_inner().saturated_into::<F>().saturating_sub(one))
.unwrap_or(F::max_value());
(result1, result2)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::proptest;
#[test]
fn compute_max_quotient_works() {
let product1 = 8625031518u64;
let product2 = 2597808837u64;
let multiplier = FixedU128::from_rational(4_000_000_000_000, 10 * 1024 * 1024);
assert_eq!(compute_max_integer_quotient(multiplier, product1), 22610);
assert_eq!(compute_max_integer_quotient(multiplier, product2), 6810);
assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product1), 22610);
assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product2), 6809);
}
#[test]
fn proptest_max_quotient_works() {
proptest!(|(numerator: u128, denominator: u128, product: u128)| {
let multiplier = FixedU128::from_rational(numerator.saturating_add(1), denominator.saturating_add(1));
let max_quotient = compute_max_integer_quotient(multiplier, product);
assert!(multiplier.saturating_mul_int(max_quotient) <= product);
if max_quotient < u128::MAX {
assert!(multiplier.saturating_mul_int(max_quotient + 1) > product);
}
});
}
#[test]
fn proptest_max_pair_quotient_works() {
proptest!(|(numerator1: u128, denominator1: u128, numerator2: u128, denominator2: u128, product: u128)| {
let multiplier1 = FixedU128::from_rational(numerator1.saturating_add(1), denominator1.saturating_add(1));
let multiplier2 = FixedU128::from_rational(numerator2.saturating_add(1), denominator2.saturating_add(1));
let (max_quotient1, max_quotient2) = compute_max_integer_pair_quotient((multiplier1, multiplier2), product);
assert!(multiplier1.saturating_mul_int(max_quotient1) <= product);
if max_quotient1 < u128::MAX {
assert!(multiplier1.saturating_mul_int(max_quotient1 + 1) > product);
}
assert!(multiplier2.saturating_mul_int(max_quotient2) <= product);
if max_quotient2 < u128::MAX {
assert!(multiplier2.saturating_mul_int(max_quotient2 + 1) > product);
}
});
}
}