3

I am attempting to build some tests that initiate and validate transaction fee movements. The problem is that I seem unable to initiate transaction fees in a mock runtime at all. I suspect I am missing something fundamental about how mocks/tests are using UncheckedExtrinsic and SignedExtra, so any help would be appreciated!

My release version of the runtime works fine to charge tx fees and transfer them to the appropriate pallet. I implemented OnUnbalanced and a custom fee splitter in the CurrencyAdapter of pallet_transaction_payment.

I moved the same fee splitter mechanism over to my mock runtime and overrode the standard Unchecked extrinsic code MockUncheckedExtrinsic, but seem unable to trigger transaction fees to even be taken in the mock runtime. Here is what is not working so far:

// type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
// type Block = frame_system::mocking::MockBlock<Test>;

// An attempt to test charging tx fees in mocks that failed :(
pub type SignedExtra = (
    frame_system::CheckWeight<Test>,
    pallet_transaction_payment::ChargeTransactionPayment<Test>,
);
pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<AccountId, Call, (), SignedExtra>;
pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;

frame_support::construct_runtime!(
    pub enum Test where
        Block = Block,
        NodeBlock = Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
        Balances: pallet_balances::{Pallet, Call, Storage, Event<T>},
        TransactionPayment: pallet_transaction_payment,
        MyPallet: my_pallet::{Pallet, Call, Storage, Event<T>},
    }
);

Thanks!

1

2 Answers 2

3

Thanks to @kianenigma and @shawn-tabrizi pointing me in the right direction, I realized that to do what I'm looking for I just need to call pre_dispatch and post_dispatch manually on the call.

In addition to adjusting the the mock as I showed above in the original question, I wrote a simple call wrapper to take the transaction fees on the calls I'd like to check. I provide it here in case anyone else is interested:

use crate::Call as PalletCall;
use pallet_transaction_payment::ChargeTransactionPayment;

fn fee_call_wrapper(call: PalletCall<Test>, from: AccountId) {
    let pallet_pre = free_balance(&MyPallet::account_id());
    let from_pre = balance(&from); // Don't care about reserves for tx fee tests

    let call: <Test as frame_system::Config>::Call = Call::MyPallet(call);
    let info = call.get_dispatch_info();
    let len = call.encode().len();
    let pre_d = ChargeTransactionPayment::<Test>::from(0).pre_dispatch(&from, &call.clone(), &info, len).expect("pre_dispatch error");
    let post_result = call.dispatch(Origin::signed(from.clone())).expect("dispatch failure");
    let actual_fee = TransactionPayment::compute_actual_fee(len.try_into().unwrap(), &info, &post_result, 0);
    assert_ok!(ChargeTransactionPayment::<Test>::post_dispatch(
        Some(pre_d),
        &info,
        &post_result,
        len,
        &Ok(())
    ));

    //Check fees
    let split = (80,20);
    let pallet_fee = actual_fee * split.0 / 100;
    assert_eq!(free_balance(&MyPallet::account_id()), pallet_fee + pallet_pre );
    assert_eq!(balance(&from), from_pre - actual_fee);

Which can then be called like so:

#[test]
fn do_something_with_tx_fees_taken_works() {
    ExtBuilder::default().build().execute_with(|| {
        let call = PalletCall::do_something{...};
        fee_call_wrapper(call, ALICE);

        // Do other checks here...
    });
}

Note that this is just a simple fee splitter, taking 80% to the MyPallet account.

1
  • Can you please tell me what the reason is for splitting?
    – mirind4
    Commented Jan 17 at 9:04
1

Your setup actually looks correct. What you have to do next is to make sure you call into the exact same functions as a real runtime when you execute a transaction.

In reality, this happens in frame-executive and in fn apply_extrinsic. To mirror that into your test setup:

  1. You probably want to skip signature verification, so you need a stub that will naively concert UncheckedExtrinsic to CheckedExtrinsic for you.
fn check(uxt: UncheckedExtrinsic) -> CheckedExtrinsic {
  // should just map the fields one to one. 
}
  1. Then, you can imitate what the executive does:
fn dispatch(uxt: UncheckedExtrinsic) {
  let xt = check(uxt); 
  let encoded_len = xt.encoded_size();
  let dispatch_info = xt.get_dispatch_info();
  let r = Applyable::apply::<_>(xt, &dispatch_info, encoded_len)?;
}

Some other relevant types implementations:

  1. Applyable.
  2. Checkable.
  3. GetDispatchInfo

Note that you have opted to use the real generic::UncheckedExtrinsic in your example, but you can probably do it with TestXt as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.