use crate::{Balances, Runtime, RuntimeCall, TransactionFees};
use frame_support::traits::fungible::Inspect;
use frame_support::traits::tokens::WithdrawConsequence;
use frame_support::traits::{Currency, ExistenceRequirement, Get, Imbalance, WithdrawReasons};
use pallet_balances::NegativeImbalance;
use parity_scale_codec::Encode;
use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Zero};
use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
use subspace_runtime_primitives::{AccountId, Balance};
pub struct TransactionByteFee;
impl Get<Balance> for TransactionByteFee {
fn get() -> Balance {
TransactionFees::transaction_byte_fee()
}
}
pub struct LiquidityInfo {
storage_fee: Balance,
imbalance: NegativeImbalance<Runtime>,
}
pub struct OnChargeTransaction;
impl pallet_transaction_payment::OnChargeTransaction<Runtime> for OnChargeTransaction {
type LiquidityInfo = Option<LiquidityInfo>;
type Balance = Balance;
fn withdraw_fee(
who: &AccountId,
call: &RuntimeCall,
_info: &DispatchInfoOf<RuntimeCall>,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
if fee.is_zero() {
return Ok(None);
}
let withdraw_reason = if tip.is_zero() {
WithdrawReasons::TRANSACTION_PAYMENT
} else {
WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
};
let withdraw_result = <Balances as Currency<AccountId>>::withdraw(
who,
fee,
withdraw_reason,
ExistenceRequirement::KeepAlive,
);
let imbalance = withdraw_result.map_err(|_error| InvalidTransaction::Payment)?;
let storage_fee = TransactionByteFee::get()
* Balance::try_from(call.encoded_size())
.expect("Size of the call never exceeds balance units; qed");
Ok(Some(LiquidityInfo {
storage_fee,
imbalance,
}))
}
fn correct_and_deposit_fee(
who: &AccountId,
_dispatch_info: &DispatchInfoOf<RuntimeCall>,
_post_info: &PostDispatchInfoOf<RuntimeCall>,
corrected_fee: Self::Balance,
tip: Self::Balance,
liquidity_info: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
if let Some(LiquidityInfo {
storage_fee,
imbalance,
}) = liquidity_info
{
let refund_amount = imbalance.peek().saturating_sub(corrected_fee);
let refund_imbalance = Balances::deposit_into_existing(who, refund_amount)
.unwrap_or_else(|_| <Balances as Currency<AccountId>>::PositiveImbalance::zero());
let adjusted_paid = imbalance
.offset(refund_imbalance)
.same()
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
let (tip, fee) = adjusted_paid.split(tip);
let (paid_storage_fee, paid_compute_fee) = fee.split(storage_fee);
TransactionFees::note_transaction_fees(
paid_storage_fee.peek(),
paid_compute_fee.peek(),
tip.peek(),
);
}
Ok(())
}
fn can_withdraw_fee(
who: &AccountId,
_call: &RuntimeCall,
_dispatch_info: &DispatchInfoOf<RuntimeCall>,
fee: Self::Balance,
_tip: Self::Balance,
) -> Result<(), TransactionValidityError> {
if fee.is_zero() {
return Ok(());
}
match Balances::can_withdraw(who, fee) {
WithdrawConsequence::Success => Ok(()),
_ => Err(InvalidTransaction::Payment.into()),
}
}
}