Skip to content

Commit

Permalink
#2029: Passkey withdraw token fees for tx (#2040)
Browse files Browse the repository at this point in the history
# Goal
The goal of this PR is to enable `pallet_transaction_pallet` to withdraw
tx fee, this is needed as our extrinsic is `unsigned`, thereby does not
gets charged by default

Closes  #2029 
Creates #2051 

# Details
- [x] Refactor some common types from `frequency-tx-payment` to
`common-primitives`
- [x] Add a withdraw fee function to be called from Validate Unsigned to
charge for tx fee
- [x] Add unit test

# Discussion
<!-- List discussion items -->

# Checklist
- [ ] Chain spec updated
- [ ] Custom RPC OR Runtime API added/changed? Updated js/api-augment.
- [ ] Design doc(s) updated
- [x] Tests added
- [ ] Benchmarks added
- [ ] Weights updated
  • Loading branch information
saraswatpuneet authored Jul 2, 2024
1 parent cf42635 commit dc4057c
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 29 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pallets/frequency-tx-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ where
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, tip);
if fee.is_zero() {
return Ok((fee, InitialPayment::Free))
return Ok((fee, InitialPayment::Free));
}

<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
Expand Down
1 change: 1 addition & 0 deletions pallets/passkey/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ sp-std = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
sp-core = { workspace = true }
log = { workspace = true, default-features = false }
pallet-transaction-payment = { workspace = true }

# Frequency related dependencies
common-primitives = { default-features = false, path = "../../common/primitives" }
Expand Down
7 changes: 5 additions & 2 deletions pallets/passkey/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type SignerId = app_sr25519::Public;

fn generate_payload<T: Config>() -> PasskeyPayload<T> {
let test_account_1_pk = SignerId::generate_pair(None);
let test_account_1_account_id =
T::AccountId::decode(&mut &test_account_1_pk.encode()[..]).unwrap();
T::Currency::set_balance(&test_account_1_account_id.clone().into(), 1000000000u32.into());
let passkey_public_key = [0u8; 33];
let wrapped_binary = wrap_binary_data(passkey_public_key.to_vec());
let signature: MultiSignature =
Expand All @@ -32,7 +35,7 @@ fn generate_payload<T: Config>() -> PasskeyPayload<T> {
frame_system::Call::<T>::remark { remark: vec![] }.into();

let call: PasskeyCall<T> = PasskeyCall {
account_id: T::AccountId::decode(&mut &test_account_1_pk.encode()[..]).unwrap(),
account_id: test_account_1_account_id,
account_nonce: T::Nonce::zero(),
account_ownership_proof: signature,
call: Box::new(inner_call),
Expand All @@ -50,7 +53,7 @@ fn generate_payload<T: Config>() -> PasskeyPayload<T> {
}

benchmarks! {
where_clause { where <T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo> }
where_clause { where <T as frame_system::Config>::RuntimeCall: From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> }

validate {
let payload = generate_payload::<T>();
Expand Down
123 changes: 110 additions & 13 deletions pallets/passkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,28 @@
rustdoc::invalid_codeblock_attributes,
missing_docs
)]

use common_primitives::utils::wrap_binary_data;
use common_runtime::extensions::check_nonce::CheckNonce;
use frame_support::{
dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
traits::Contains,
};
use frame_system::pallet_prelude::*;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
traits::{Convert, Dispatchable, SignedExtension, Verify},
traits::{Convert, DispatchInfoOf, Dispatchable, SignedExtension, Verify, Zero},
transaction_validity::{TransactionValidity, TransactionValidityError},
AccountId32, MultiSignature,
};
use sp_std::{vec, vec::Vec};

use common_runtime::extensions::check_nonce::CheckNonce;
/// Type aliases used for interaction with `OnChargeTransaction`.
pub(crate) type OnChargeTransactionOf<T> =
<T as pallet_transaction_payment::Config>::OnChargeTransaction;

/// Balance type alias.
pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;

#[cfg(test)]
mod mock;
Expand All @@ -40,6 +47,9 @@ mod tests;
pub mod weights;
pub use weights::*;

#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

Expand All @@ -58,7 +68,7 @@ pub mod module {
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);

#[pallet::config]
pub trait Config: frame_system::Config {
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

Expand All @@ -67,7 +77,8 @@ pub mod module {
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
+ IsType<<Self as frame_system::Config>::RuntimeCall>
+ From<Call<Self>>;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
Expand All @@ -77,6 +88,10 @@ pub mod module {

/// Filters the inner calls for passkey which is set in runtime
type PasskeyCallFilter: Contains<<Self as Config>::RuntimeCall>;

/// Helper Curreny method for benchmarking
#[cfg(feature = "runtime-benchmarks")]
type Currency: Mutate<Self::AccountId>;
}

#[pallet::error]
Expand Down Expand Up @@ -131,28 +146,46 @@ pub mod module {
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T>
where
<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo>,
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let valid_tx = ValidTransaction::default();
let payload = Self::filter_valid_calls(&call)?;
Self::validate_signatures(&payload)?;

let signature_validity = Self::validate_signatures(&payload)?;
let nonce_check = PasskeyNonce::new(payload.passkey_call.clone());
nonce_check.validate()
let nonce_validity = nonce_check.validate()?;
let tx_charge = ChargeTransactionPayment::<T>(
payload.passkey_call.account_id.clone(),
call.clone(),
);
let tx_payment_validity = tx_charge.validate()?;
let valid_tx = valid_tx
.combine_with(signature_validity)
.combine_with(nonce_validity)
.combine_with(tx_payment_validity);
Ok(valid_tx)
}

fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
Self::validate_unsigned(TransactionSource::InBlock, call)?;

let payload = Self::filter_valid_calls(&call)?;
Self::validate_signatures(&payload)?;
let nonce_check = PasskeyNonce::new(payload.passkey_call.clone());
nonce_check.pre_dispatch()
nonce_check.pre_dispatch()?;
let tx_charge = ChargeTransactionPayment::<T>(
payload.passkey_call.account_id.clone(),
call.clone(),
);
tx_charge.pre_dispatch()
}
}
}

impl<T: Config> Pallet<T> {
impl<T: Config> Pallet<T>
where
<T as frame_system::Config>::RuntimeCall: From<Call<T>> + Dispatchable<Info = DispatchInfo>,
{
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayload<T>, TransactionValidityError> {
match call {
Call::proxy { payload }
Expand Down Expand Up @@ -232,3 +265,67 @@ where
passkey_nonce.pre_dispatch(&who, &some_call.clone().into(), info, 0usize)
}
}

/// Passkey related tx payment
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeTransactionPayment<T: Config>(pub T::AccountId, pub Call<T>);

impl<T: Config> ChargeTransactionPayment<T>
where
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
/// Validates the transaction fee paid with tokens.
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let info = &self.1.get_dispatch_info();
let len = self.0.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
self.withdraw_token_fee(&who, &runtime_call, info, len, Zero::zero())?;
Ok(())
}

/// Validates the transaction fee paid with tokens.
pub fn validate(&self) -> TransactionValidity {
let info = &self.1.get_dispatch_info();
let len = self.0.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
let fee = self.withdraw_token_fee(&who, &runtime_call, info, len, Zero::zero())?;

let priority = pallet_transaction_payment::ChargeTransactionPayment::<T>::get_priority(
info,
len,
Zero::zero(),
fee,
);

Ok(ValidTransaction { priority, ..Default::default() })
}

/// Withdraws transaction fee paid with tokens.
/// # Arguments
/// * `who` - The account id of the payer
/// * `call` - The call
/// # Return
/// * `Ok((fee, initial_payment))` if the fee is successfully withdrawn
/// * `Err(InvalidTransaction::Payment)` if the fee cannot be withdrawn
fn withdraw_token_fee(
&self,
who: &T::AccountId,
call: &<T as frame_system::Config>::RuntimeCall,
info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
len: usize,
tip: BalanceOf<T>,
) -> Result<BalanceOf<T>, TransactionValidityError> {
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, tip);
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
who, call, info, fee, tip,
)
.map(|_| (fee))
.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
}
}
48 changes: 43 additions & 5 deletions pallets/passkey/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
//! Mocks for the Passkey module.
use crate as pallet_passkey;
use crate::*;
use frame_support::{
construct_runtime,
construct_runtime, parameter_types,
traits::{ConstU32, ConstU64, Contains, Everything},
weights::WeightToFee as WeightToFeeTrait,
};
use sp_core::H256;
use pallet_transaction_payment::CurrencyAdapter;
use sp_core::{ConstU8, H256};
use sp_runtime::{
traits::{ConvertInto, IdentityLookup},
BuildStorage,
BuildStorage, SaturatedConversion,
};

use crate as pallet_passkey;

use common_primitives::node::AccountId;

type Block = frame_system::mocking::MockBlockU32<Test>;

// Needs parameter_types! for the impls below
parameter_types! {
pub static WeightToFee: u64 = 1;
pub static TransactionByteFee: u64 = 1;
}

impl WeightToFeeTrait for WeightToFee {
type Balance = u64;

fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::Balance::saturated_from(weight.ref_time())
.saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow()))
}
}

impl WeightToFeeTrait for TransactionByteFee {
type Balance = u64;

fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::Balance::saturated_from(weight.ref_time())
.saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow()))
}
}

construct_runtime!(
pub enum Test
{
System: frame_system::{Pallet, Call, Storage, Config<T>, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Passkey: pallet_passkey::{Pallet, Storage, Call, Event<T>, ValidateUnsigned},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
}
);

Expand Down Expand Up @@ -51,12 +78,23 @@ impl frame_system::Config for Test {
type MaxConsumers = ConstU32<16>;
}

impl pallet_transaction_payment::Config for Test {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
type WeightToFee = WeightToFee;
type LengthToFee = TransactionByteFee;
type FeeMultiplierUpdate = ();
type OperationalFeeMultiplier = ConstU8<5>;
}

impl pallet_passkey::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type RuntimeCall = RuntimeCall;
type ConvertIntoAccountId32 = ConvertInto;
type PasskeyCallFilter = MockPasskeyCallFilter;
#[cfg(feature = "runtime-benchmarks")]
type Currency = Balances;
}

impl pallet_balances::Config for Test {
Expand Down
Loading

0 comments on commit dc4057c

Please sign in to comment.