mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 13:07:56 +00:00
Adds syntax for marking calls feeless (#1926)
Fixes https://github.com/paritytech/polkadot-sdk/issues/1725 This PR adds the following changes: 1. An attribute `pallet::feeless_if` that can be optionally attached to a call like so: ```rust #[pallet::feeless_if(|_origin: &OriginFor<T>, something: &u32| -> bool { *something == 0 })] pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult { .... } ``` The closure passed accepts references to arguments as specified in the call fn. It returns a boolean that denotes the conditions required for this call to be "feeless". 2. A signed extension `SkipCheckIfFeeless<T: SignedExtension>` that wraps a transaction payment processor such as `pallet_transaction_payment::ChargeTransactionPayment`. It checks for all calls annotated with `pallet::feeless_if` to see if the conditions are met. If so, the wrapped signed extension is not called, essentially making the call feeless. In order to use this, you can simply replace your existing signed extension that manages transaction payment like so: ```diff - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + pallet_skip_feeless_payment::SkipCheckIfFeeless< + Runtime, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + >, ``` ### Todo - [x] Tests - [x] Docs - [x] Prdoc --------- Co-authored-by: Nikhil Gupta <> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
This commit is contained in:
Generated
+15
@@ -7224,6 +7224,7 @@ dependencies = [
|
||||
"pallet-scheduler",
|
||||
"pallet-session",
|
||||
"pallet-session-benchmarking",
|
||||
"pallet-skip-feeless-payment",
|
||||
"pallet-society",
|
||||
"pallet-staking",
|
||||
"pallet-staking-reward-curve",
|
||||
@@ -8853,6 +8854,7 @@ dependencies = [
|
||||
"pallet-asset-conversion-tx-payment",
|
||||
"pallet-asset-tx-payment",
|
||||
"pallet-assets",
|
||||
"pallet-skip-feeless-payment",
|
||||
"parity-scale-codec",
|
||||
"sc-block-builder",
|
||||
"sc-client-api",
|
||||
@@ -10795,6 +10797,18 @@ dependencies = [
|
||||
"sp-std 8.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-skip-feeless-payment"
|
||||
version = "1.0.0-dev"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-runtime",
|
||||
"sp-std 8.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-society"
|
||||
version = "4.0.0-dev"
|
||||
@@ -18203,6 +18217,7 @@ dependencies = [
|
||||
"pallet-assets",
|
||||
"pallet-balances",
|
||||
"pallet-im-online",
|
||||
"pallet-skip-feeless-payment",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"platforms",
|
||||
|
||||
@@ -382,6 +382,7 @@ members = [
|
||||
"substrate/frame/transaction-payment/asset-tx-payment",
|
||||
"substrate/frame/transaction-payment/rpc",
|
||||
"substrate/frame/transaction-payment/rpc/runtime-api",
|
||||
"substrate/frame/transaction-payment/skip-feeless-payment",
|
||||
"substrate/frame/transaction-storage",
|
||||
"substrate/frame/treasury",
|
||||
"substrate/frame/try-runtime",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
title: Adds syntax for marking calls feeless
|
||||
|
||||
doc:
|
||||
- audience: Core Dev
|
||||
description: |
|
||||
1. Adds an attribute `#[pallet::feeless_if]` that can be optionally attached to a `pallet::call`.
|
||||
2. Adds a signed extension SkipCheckIfFeeless<T: SignedExtension> that wraps a transaction
|
||||
payment processor to potentially skip payment fees for such calls.
|
||||
Note that both the attribute and the signed extension are needed to make the call feeless.
|
||||
|
||||
migrations:
|
||||
db: []
|
||||
|
||||
runtime: []
|
||||
|
||||
crates:
|
||||
- name: "frame-support-procedural"
|
||||
semver: minor
|
||||
- name: "pallet-skip-feeless-payment"
|
||||
semver: major
|
||||
- pallet-example-kitchensink
|
||||
semver: patch
|
||||
- kitchensink-runtime
|
||||
semver: major
|
||||
- node-testing
|
||||
semver: patch
|
||||
- node-cli
|
||||
semver: patch
|
||||
|
||||
host_functions: []
|
||||
@@ -96,6 +96,7 @@ pallet-assets = { path = "../../../frame/assets" }
|
||||
pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment" }
|
||||
pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment" }
|
||||
pallet-im-online = { path = "../../../frame/im-online", default-features = false}
|
||||
pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment", default-features = false}
|
||||
|
||||
# node-specific dependencies
|
||||
kitchensink-runtime = { path = "../runtime" }
|
||||
@@ -168,6 +169,7 @@ runtime-benchmarks = [
|
||||
"pallet-assets/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-im-online/runtime-benchmarks",
|
||||
"pallet-skip-feeless-payment/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
"sc-client-db/runtime-benchmarks",
|
||||
"sc-service/runtime-benchmarks",
|
||||
@@ -183,6 +185,7 @@ try-runtime = [
|
||||
"pallet-assets/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
"pallet-skip-feeless-payment/try-runtime",
|
||||
"pallet-timestamp/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
"substrate-cli-test-utils/try-runtime",
|
||||
|
||||
@@ -91,21 +91,24 @@ pub fn create_extrinsic(
|
||||
.map(|c| c / 2)
|
||||
.unwrap_or(2) as u64;
|
||||
let tip = 0;
|
||||
let extra: kitchensink_runtime::SignedExtra = (
|
||||
frame_system::CheckNonZeroSender::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckGenesis::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckEra::<kitchensink_runtime::Runtime>::from(generic::Era::mortal(
|
||||
period,
|
||||
best_block.saturated_into(),
|
||||
)),
|
||||
frame_system::CheckNonce::<kitchensink_runtime::Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<kitchensink_runtime::Runtime>::new(),
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<kitchensink_runtime::Runtime>::from(
|
||||
tip, None,
|
||||
),
|
||||
);
|
||||
let extra: kitchensink_runtime::SignedExtra =
|
||||
(
|
||||
frame_system::CheckNonZeroSender::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckGenesis::<kitchensink_runtime::Runtime>::new(),
|
||||
frame_system::CheckEra::<kitchensink_runtime::Runtime>::from(generic::Era::mortal(
|
||||
period,
|
||||
best_block.saturated_into(),
|
||||
)),
|
||||
frame_system::CheckNonce::<kitchensink_runtime::Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<kitchensink_runtime::Runtime>::new(),
|
||||
pallet_skip_feeless_payment::SkipCheckIfFeeless::from(
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<
|
||||
kitchensink_runtime::Runtime,
|
||||
>::from(tip, None),
|
||||
),
|
||||
);
|
||||
|
||||
let raw_payload = kitchensink_runtime::SignedPayload::from_raw(
|
||||
function.clone(),
|
||||
@@ -879,8 +882,9 @@ mod tests {
|
||||
let check_era = frame_system::CheckEra::from(Era::Immortal);
|
||||
let check_nonce = frame_system::CheckNonce::from(index);
|
||||
let check_weight = frame_system::CheckWeight::new();
|
||||
let tx_payment =
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None);
|
||||
let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from(
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None),
|
||||
);
|
||||
let extra = (
|
||||
check_non_zero_sender,
|
||||
check_spec_version,
|
||||
|
||||
@@ -129,6 +129,7 @@ pallet-transaction-payment = { path = "../../../frame/transaction-payment", defa
|
||||
pallet-transaction-payment-rpc-runtime-api = { path = "../../../frame/transaction-payment/rpc/runtime-api", default-features = false}
|
||||
pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment", default-features = false}
|
||||
pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment", default-features = false}
|
||||
pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment", default-features = false}
|
||||
pallet-transaction-storage = { path = "../../../frame/transaction-storage", default-features = false}
|
||||
pallet-uniques = { path = "../../../frame/uniques", default-features = false}
|
||||
pallet-vesting = { path = "../../../frame/vesting", default-features = false}
|
||||
@@ -212,6 +213,7 @@ std = [
|
||||
"pallet-scheduler/std",
|
||||
"pallet-session-benchmarking?/std",
|
||||
"pallet-session/std",
|
||||
"pallet-skip-feeless-payment/std",
|
||||
"pallet-society/std",
|
||||
"pallet-staking-runtime-api/std",
|
||||
"pallet-staking/std",
|
||||
@@ -308,6 +310,7 @@ runtime-benchmarks = [
|
||||
"pallet-salary/runtime-benchmarks",
|
||||
"pallet-scheduler/runtime-benchmarks",
|
||||
"pallet-session-benchmarking/runtime-benchmarks",
|
||||
"pallet-skip-feeless-payment/runtime-benchmarks",
|
||||
"pallet-society/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
"pallet-state-trie-migration/runtime-benchmarks",
|
||||
@@ -381,6 +384,7 @@ try-runtime = [
|
||||
"pallet-salary/try-runtime",
|
||||
"pallet-scheduler/try-runtime",
|
||||
"pallet-session/try-runtime",
|
||||
"pallet-skip-feeless-payment/try-runtime",
|
||||
"pallet-society/try-runtime",
|
||||
"pallet-staking/try-runtime",
|
||||
"pallet-state-trie-migration/try-runtime",
|
||||
|
||||
@@ -569,6 +569,10 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime {
|
||||
pallet_asset_conversion_tx_payment::AssetConversionAdapter<Balances, AssetConversion>;
|
||||
}
|
||||
|
||||
impl pallet_skip_feeless_payment::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: Moment = SLOT_DURATION / 2;
|
||||
}
|
||||
@@ -1394,7 +1398,11 @@ where
|
||||
frame_system::CheckEra::<Runtime>::from(era),
|
||||
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),
|
||||
pallet_skip_feeless_payment::SkipCheckIfFeeless::from(
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(
|
||||
tip, None,
|
||||
),
|
||||
),
|
||||
);
|
||||
let raw_payload = SignedPayload::new(call, extra)
|
||||
.map_err(|e| {
|
||||
@@ -2134,6 +2142,7 @@ construct_runtime!(
|
||||
Statement: pallet_statement,
|
||||
Broker: pallet_broker,
|
||||
Mixnet: pallet_mixnet,
|
||||
SkipFeelessPayment: pallet_skip_feeless_payment,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2160,7 +2169,10 @@ pub type SignedExtra = (
|
||||
frame_system::CheckEra<Runtime>,
|
||||
frame_system::CheckNonce<Runtime>,
|
||||
frame_system::CheckWeight<Runtime>,
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>,
|
||||
pallet_skip_feeless_payment::SkipCheckIfFeeless<
|
||||
Runtime,
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>,
|
||||
>,
|
||||
);
|
||||
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
|
||||
@@ -26,6 +26,7 @@ pallet-asset-conversion = { path = "../../../frame/asset-conversion" }
|
||||
pallet-assets = { path = "../../../frame/assets" }
|
||||
pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment" }
|
||||
pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment" }
|
||||
pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment" }
|
||||
sc-block-builder = { path = "../../../client/block-builder" }
|
||||
sc-client-api = { path = "../../../client/api" }
|
||||
sc-client-db = { path = "../../../client/db", features = ["rocksdb"]}
|
||||
|
||||
@@ -78,7 +78,9 @@ pub fn signed_extra(nonce: Nonce, extra_fee: Balance) -> SignedExtra {
|
||||
frame_system::CheckEra::from(Era::mortal(256, 0)),
|
||||
frame_system::CheckNonce::from(nonce),
|
||||
frame_system::CheckWeight::new(),
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None),
|
||||
pallet_skip_feeless_payment::SkipCheckIfFeeless::from(
|
||||
pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,10 @@ pub mod pallet {
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::set_foo_benchmark())]
|
||||
/// Marks this call as feeless if `new_foo` is zero.
|
||||
#[pallet::feeless_if(|_origin: &OriginFor<T>, new_foo: &u32, _other_compact: &u128| -> bool {
|
||||
*new_foo == 0
|
||||
})]
|
||||
pub fn set_foo(
|
||||
_: OriginFor<T>,
|
||||
new_foo: u32,
|
||||
|
||||
@@ -124,6 +124,18 @@ pub fn expand_outer_dispatch(
|
||||
}
|
||||
}
|
||||
|
||||
impl #scrate::dispatch::CheckIfFeeless for RuntimeCall {
|
||||
type Origin = #system_path::pallet_prelude::OriginFor<#runtime>;
|
||||
fn is_feeless(&self, origin: &Self::Origin) -> bool {
|
||||
match self {
|
||||
#(
|
||||
#pallet_attrs
|
||||
#variant_patterns => call.is_feeless(origin),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #scrate::traits::GetCallMetadata for RuntimeCall {
|
||||
fn get_call_metadata(&self) -> #scrate::traits::CallMetadata {
|
||||
use #scrate::traits::GetCallName;
|
||||
|
||||
@@ -1157,6 +1157,36 @@ pub fn call_index(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
/// Each dispatchable may be annotated with the `#[pallet::feeless_if($closure)]` attribute,
|
||||
/// which explicitly defines the condition for the dispatchable to be feeless.
|
||||
///
|
||||
/// The arguments for the closure must be the referenced arguments of the dispatchable function.
|
||||
///
|
||||
/// The closure must return `bool`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// #[pallet::feeless_if(|_origin: &OriginFor<T>, something: &u32| -> bool {
|
||||
/// *something == 0
|
||||
/// })]
|
||||
/// pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
|
||||
/// ....
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Please note that this only works for signed dispatchables and requires a signed extension
|
||||
/// such as `SkipCheckIfFeeless` as defined in `pallet-skip-feeless-payment` to wrap the existing
|
||||
/// payment extension. Else, this is completely ignored and the dispatchable is still charged.
|
||||
///
|
||||
/// ### Macro expansion
|
||||
///
|
||||
/// The macro implements the `CheckIfFeeless` trait on the dispatchable and calls the corresponding
|
||||
/// closure in the implementation.
|
||||
#[proc_macro_attribute]
|
||||
pub fn feeless_if(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
pallet_macro_stub()
|
||||
}
|
||||
|
||||
/// Allows you to define some extra constants to be added into constant metadata.
|
||||
///
|
||||
/// Item must be defined as:
|
||||
|
||||
@@ -241,6 +241,16 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let feeless_check = methods.iter().map(|method| &method.feeless_check).collect::<Vec<_>>();
|
||||
let feeless_check_result =
|
||||
feeless_check.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| {
|
||||
if let Some(feeless_check) = feeless_check {
|
||||
quote::quote!(#feeless_check(origin, #( #arg_name, )*))
|
||||
} else {
|
||||
quote::quote!(false)
|
||||
}
|
||||
});
|
||||
|
||||
quote::quote_spanned!(span =>
|
||||
mod warnings {
|
||||
#(
|
||||
@@ -347,6 +357,23 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl<#type_impl_gen> #frame_support::dispatch::CheckIfFeeless for #call_ident<#type_use_gen>
|
||||
#where_clause
|
||||
{
|
||||
type Origin = #frame_system::pallet_prelude::OriginFor<T>;
|
||||
#[allow(unused_variables)]
|
||||
fn is_feeless(&self, origin: &Self::Origin) -> bool {
|
||||
match *self {
|
||||
#(
|
||||
Self::#fn_name { #( #args_name_pattern_ref, )* } => {
|
||||
#feeless_check_result
|
||||
},
|
||||
)*
|
||||
Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<#type_impl_gen> #frame_support::traits::GetCallName for #call_ident<#type_use_gen>
|
||||
#where_clause
|
||||
{
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
|
||||
use super::{helper, InheritedCallWeightAttr};
|
||||
use frame_support_procedural_tools::get_doc_literals;
|
||||
use proc_macro2::Span;
|
||||
use quote::ToTokens;
|
||||
use std::collections::HashMap;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{spanned::Spanned, ExprClosure};
|
||||
|
||||
/// List of additional token to be used for parsing.
|
||||
mod keyword {
|
||||
@@ -30,6 +31,7 @@ mod keyword {
|
||||
syn::custom_keyword!(compact);
|
||||
syn::custom_keyword!(T);
|
||||
syn::custom_keyword!(pallet);
|
||||
syn::custom_keyword!(feeless_if);
|
||||
}
|
||||
|
||||
/// Definition of dispatchables typically `impl<T: Config> Pallet<T> { ... }`
|
||||
@@ -82,13 +84,18 @@ pub struct CallVariantDef {
|
||||
pub docs: Vec<syn::Expr>,
|
||||
/// Attributes annotated at the top of the dispatchable function.
|
||||
pub attrs: Vec<syn::Attribute>,
|
||||
/// The optional `feeless_if` attribute on the `pallet::call`.
|
||||
pub feeless_check: Option<syn::ExprClosure>,
|
||||
}
|
||||
|
||||
/// Attributes for functions in call impl block.
|
||||
/// Parse for `#[pallet::weight(expr)]` or `#[pallet::call_index(expr)]
|
||||
pub enum FunctionAttr {
|
||||
/// Parse for `#[pallet::call_index(expr)]`
|
||||
CallIndex(u8),
|
||||
/// Parse for `#[pallet::weight(expr)]`
|
||||
Weight(syn::Expr),
|
||||
/// Parse for `#[pallet::feeless_if(expr)]`
|
||||
FeelessIf(Span, syn::ExprClosure),
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for FunctionAttr {
|
||||
@@ -115,6 +122,19 @@ impl syn::parse::Parse for FunctionAttr {
|
||||
return Err(syn::Error::new(index.span(), msg))
|
||||
}
|
||||
Ok(FunctionAttr::CallIndex(index.base10_parse()?))
|
||||
} else if lookahead.peek(keyword::feeless_if) {
|
||||
content.parse::<keyword::feeless_if>()?;
|
||||
let closure_content;
|
||||
syn::parenthesized!(closure_content in content);
|
||||
Ok(FunctionAttr::FeelessIf(
|
||||
closure_content.span(),
|
||||
closure_content.parse::<syn::ExprClosure>().map_err(|e| {
|
||||
let msg = "Invalid feeless_if attribute: expected a closure";
|
||||
let mut err = syn::Error::new(closure_content.span(), msg);
|
||||
err.combine(e);
|
||||
err
|
||||
})?,
|
||||
))
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
@@ -138,28 +158,33 @@ impl syn::parse::Parse for ArgAttrIsCompact {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the syntax is `OriginFor<T>`
|
||||
pub fn check_dispatchable_first_arg_type(ty: &syn::Type) -> syn::Result<()> {
|
||||
pub struct CheckDispatchableFirstArg;
|
||||
/// Check the syntax is `OriginFor<T>` or `&OriginFor<T>`.
|
||||
pub fn check_dispatchable_first_arg_type(ty: &syn::Type, is_ref: bool) -> syn::Result<()> {
|
||||
pub struct CheckDispatchableFirstArg(bool);
|
||||
impl syn::parse::Parse for CheckDispatchableFirstArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let is_ref = input.parse::<syn::Token![&]>().is_ok();
|
||||
input.parse::<keyword::OriginFor>()?;
|
||||
input.parse::<syn::Token![<]>()?;
|
||||
input.parse::<keyword::T>()?;
|
||||
input.parse::<syn::Token![>]>()?;
|
||||
|
||||
Ok(Self)
|
||||
Ok(Self(is_ref))
|
||||
}
|
||||
}
|
||||
|
||||
syn::parse2::<CheckDispatchableFirstArg>(ty.to_token_stream()).map_err(|e| {
|
||||
let msg = "Invalid type: expected `OriginFor<T>`";
|
||||
let mut err = syn::Error::new(ty.span(), msg);
|
||||
err.combine(e);
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
let result = syn::parse2::<CheckDispatchableFirstArg>(ty.to_token_stream());
|
||||
return match result {
|
||||
Ok(CheckDispatchableFirstArg(has_ref)) if is_ref == has_ref => Ok(()),
|
||||
_ => {
|
||||
let msg = if is_ref {
|
||||
"Invalid type: expected `&OriginFor<T>`"
|
||||
} else {
|
||||
"Invalid type: expected `OriginFor<T>`"
|
||||
};
|
||||
return Err(syn::Error::new(ty.span(), msg))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl CallDef {
|
||||
@@ -215,7 +240,7 @@ impl CallDef {
|
||||
return Err(syn::Error::new(method.sig.span(), msg))
|
||||
},
|
||||
Some(syn::FnArg::Typed(arg)) => {
|
||||
check_dispatchable_first_arg_type(&arg.ty)?;
|
||||
check_dispatchable_first_arg_type(&arg.ty, false)?;
|
||||
},
|
||||
}
|
||||
|
||||
@@ -227,16 +252,22 @@ impl CallDef {
|
||||
return Err(syn::Error::new(method.sig.span(), msg))
|
||||
}
|
||||
|
||||
let (mut weight_attrs, mut call_idx_attrs): (Vec<FunctionAttr>, Vec<FunctionAttr>) =
|
||||
helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter().partition(
|
||||
|attr| {
|
||||
if let FunctionAttr::Weight(_) = attr {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
let mut call_idx_attrs = vec![];
|
||||
let mut weight_attrs = vec![];
|
||||
let mut feeless_attrs = vec![];
|
||||
for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() {
|
||||
match attr {
|
||||
FunctionAttr::CallIndex(_) => {
|
||||
call_idx_attrs.push(attr);
|
||||
},
|
||||
);
|
||||
FunctionAttr::Weight(_) => {
|
||||
weight_attrs.push(attr);
|
||||
},
|
||||
FunctionAttr::FeelessIf(span, _) => {
|
||||
feeless_attrs.push((span, attr));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if weight_attrs.is_empty() && dev_mode {
|
||||
// inject a default O(1) weight when dev mode is enabled and no weight has
|
||||
@@ -323,6 +354,73 @@ impl CallDef {
|
||||
|
||||
let docs = get_doc_literals(&method.attrs);
|
||||
|
||||
if feeless_attrs.len() > 1 {
|
||||
let msg = "Invalid pallet::call, there can only be one feeless_if attribute";
|
||||
return Err(syn::Error::new(feeless_attrs[1].0, msg))
|
||||
}
|
||||
let feeless_check: Option<ExprClosure> =
|
||||
feeless_attrs.pop().map(|(_, attr)| match attr {
|
||||
FunctionAttr::FeelessIf(_, closure) => closure,
|
||||
_ => unreachable!("checked during creation of the let binding"),
|
||||
});
|
||||
|
||||
if let Some(ref feeless_check) = feeless_check {
|
||||
if feeless_check.inputs.len() != args.len() + 1 {
|
||||
let msg = "Invalid pallet::call, feeless_if closure must have same \
|
||||
number of arguments as the dispatchable function";
|
||||
return Err(syn::Error::new(feeless_check.span(), msg))
|
||||
}
|
||||
|
||||
match feeless_check.inputs.first() {
|
||||
None => {
|
||||
let msg = "Invalid pallet::call, feeless_if closure must have at least origin arg";
|
||||
return Err(syn::Error::new(feeless_check.span(), msg))
|
||||
},
|
||||
Some(syn::Pat::Type(arg)) => {
|
||||
check_dispatchable_first_arg_type(&arg.ty, true)?;
|
||||
},
|
||||
_ => {
|
||||
let msg = "Invalid pallet::call, feeless_if closure first argument must be a typed argument, \
|
||||
e.g. `origin: OriginFor<T>`";
|
||||
return Err(syn::Error::new(feeless_check.span(), msg))
|
||||
},
|
||||
}
|
||||
|
||||
for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) {
|
||||
let feeless_arg_type =
|
||||
if let syn::Pat::Type(syn::PatType { ty, .. }) = feeless_arg.clone() {
|
||||
if let syn::Type::Reference(pat) = *ty {
|
||||
pat.elem.clone()
|
||||
} else {
|
||||
let msg = "Invalid pallet::call, feeless_if closure argument must be a reference";
|
||||
return Err(syn::Error::new(ty.span(), msg))
|
||||
}
|
||||
} else {
|
||||
let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern";
|
||||
return Err(syn::Error::new(feeless_arg.span(), msg))
|
||||
};
|
||||
|
||||
if feeless_arg_type != arg.2 {
|
||||
let msg =
|
||||
"Invalid pallet::call, feeless_if closure argument must have \
|
||||
a reference to the same type as the dispatchable function argument";
|
||||
return Err(syn::Error::new(feeless_arg.span(), msg))
|
||||
}
|
||||
}
|
||||
|
||||
let valid_return = match &feeless_check.output {
|
||||
syn::ReturnType::Type(_, type_) => match *(type_.clone()) {
|
||||
syn::Type::Path(syn::TypePath { path, .. }) => path.is_ident("bool"),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if !valid_return {
|
||||
let msg = "Invalid pallet::call, feeless_if closure must return `bool`";
|
||||
return Err(syn::Error::new(feeless_check.output.span(), msg))
|
||||
}
|
||||
}
|
||||
|
||||
methods.push(CallVariantDef {
|
||||
name: method.sig.ident.clone(),
|
||||
weight,
|
||||
@@ -331,6 +429,7 @@ impl CallDef {
|
||||
args,
|
||||
docs,
|
||||
attrs: method.attrs.clone(),
|
||||
feeless_check,
|
||||
});
|
||||
} else {
|
||||
let msg = "Invalid pallet::call, only method accepted";
|
||||
|
||||
@@ -54,6 +54,20 @@ pub trait Callable<T> {
|
||||
// https://github.com/rust-lang/rust/issues/51331
|
||||
pub type CallableCallFor<A, R> = <A as Callable<R>>::RuntimeCall;
|
||||
|
||||
/// Means to checks if the dispatchable is feeless.
|
||||
///
|
||||
/// This is automatically implemented for all dispatchables during pallet expansion.
|
||||
/// If a call is marked by [`#[pallet::feeless_if]`](`macro@frame_support_procedural::feeless_if`)
|
||||
/// attribute, the corresponding closure is checked.
|
||||
pub trait CheckIfFeeless {
|
||||
/// The Origin type of the runtime.
|
||||
type Origin;
|
||||
|
||||
/// Checks if the dispatchable satisfies the feeless condition as defined by
|
||||
/// [`#[pallet::feeless_if]`](`macro@frame_support_procedural::feeless_if`)
|
||||
fn is_feeless(&self, origin: &Self::Origin) -> bool;
|
||||
}
|
||||
|
||||
/// Origin for the System pallet.
|
||||
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub enum RawOrigin<AccountId> {
|
||||
|
||||
@@ -2227,9 +2227,10 @@ pub use frame_support_procedural::pallet;
|
||||
pub mod pallet_macros {
|
||||
pub use frame_support_procedural::{
|
||||
call_index, compact, composite_enum, config, disable_frame_system_supertrait_check, error,
|
||||
event, extra_constants, generate_deposit, generate_store, getter, hooks, import_section,
|
||||
inherent, no_default, no_default_bounds, origin, pallet_section, storage_prefix,
|
||||
storage_version, type_value, unbounded, validate_unsigned, weight, whitelist_storage,
|
||||
event, extra_constants, feeless_if, generate_deposit, generate_store, getter, hooks,
|
||||
import_section, inherent, no_default, no_default_bounds, origin, pallet_section,
|
||||
storage_prefix, storage_version, type_value, unbounded, validate_unsigned, weight,
|
||||
whitelist_storage,
|
||||
};
|
||||
|
||||
/// Allows you to define the genesis configuration for the pallet.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|| -> bool { true })]
|
||||
pub fn foo(_: OriginFor<T>) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Invalid pallet::call, feeless_if closure must have same number of arguments as the dispatchable function
|
||||
--> tests/pallet_ui/call_feeless_invalid_closure_arg1.rs:31:24
|
||||
|
|
||||
31 | #[pallet::feeless_if(|| -> bool { true })]
|
||||
| ^
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|_: bool| -> bool { true })]
|
||||
pub fn foo(_: OriginFor<T>) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Invalid type: expected `&OriginFor<T>`
|
||||
--> tests/pallet_ui/call_feeless_invalid_closure_arg2.rs:31:28
|
||||
|
|
||||
31 | #[pallet::feeless_if(|_: bool| -> bool { true })]
|
||||
| ^^^^
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|_: &OriginFor<T>, _s: &u32| -> bool { true })]
|
||||
pub fn foo(_: OriginFor<T>, _something: u64) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Invalid pallet::call, feeless_if closure argument must have a reference to the same type as the dispatchable function argument
|
||||
--> tests/pallet_ui/call_feeless_invalid_closure_arg3.rs:31:43
|
||||
|
|
||||
31 | #[pallet::feeless_if(|_: &OriginFor<T>, _s: &u32| -> bool { true })]
|
||||
| ^^
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|_: &OriginFor<T>| -> u32 { 0 })]
|
||||
pub fn foo(_: OriginFor<T>) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
error: Invalid pallet::call, feeless_if closure must return `bool`
|
||||
--> tests/pallet_ui/call_feeless_invalid_closure_return.rs:31:43
|
||||
|
|
||||
31 | #[pallet::feeless_if(|_: &OriginFor<T>| -> u32 { 0 })]
|
||||
| ^
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(0)]
|
||||
pub fn foo(_: OriginFor<T>) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: Invalid feeless_if attribute: expected a closure
|
||||
--> tests/pallet_ui/call_feeless_invalid_type.rs:31:24
|
||||
|
|
||||
31 | #[pallet::feeless_if(0)]
|
||||
| ^
|
||||
|
||||
error: expected `|`
|
||||
--> tests/pallet_ui/call_feeless_invalid_type.rs:31:24
|
||||
|
|
||||
31 | #[pallet::feeless_if(0)]
|
||||
| ^
|
||||
@@ -1,4 +1,4 @@
|
||||
error: expected `weight` or `call_index`
|
||||
error: expected one of: `weight`, `call_index`, `feeless_if`
|
||||
--> tests/pallet_ui/call_invalid_attr.rs:31:13
|
||||
|
|
||||
31 | #[pallet::weird_attr]
|
||||
|
||||
@@ -3,9 +3,3 @@ error: Invalid type: expected `OriginFor<T>`
|
||||
|
|
||||
34 | pub fn foo(origin: u8) {}
|
||||
| ^^
|
||||
|
||||
error: expected `OriginFor`
|
||||
--> tests/pallet_ui/call_invalid_origin_type.rs:34:22
|
||||
|
|
||||
34 | pub fn foo(origin: u8) {}
|
||||
| ^^
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
mod pallet {
|
||||
use frame_support::pallet_prelude::DispatchResult;
|
||||
use frame_system::pallet_prelude::OriginFor;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|_: &OriginFor<T>| -> bool { true })]
|
||||
pub fn foo(_: OriginFor<T>) -> DispatchResult { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "pallet-skip-feeless-payment"
|
||||
version = "1.0.0-dev"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions are satisfied."
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# Substrate dependencies
|
||||
sp-runtime = { path = "../../../primitives/runtime", default-features = false}
|
||||
sp-std = { path = "../../../primitives/std", default-features = false}
|
||||
|
||||
frame-support = { path = "../../support", default-features = false}
|
||||
frame-system = { path = "../../system", default-features = false}
|
||||
|
||||
# Other dependencies
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"scale-info/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! # Skip Feeless Payment Pallet
|
||||
//!
|
||||
//! This pallet allows runtimes that include it to skip payment of transaction fees for
|
||||
//! dispatchables marked by [`#[pallet::feeless_if]`](`macro@
|
||||
//! frame_support::pallet_prelude::feeless_if`).
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! It does this by wrapping an existing [`SignedExtension`] implementation (e.g.
|
||||
//! [`pallet-transaction-payment`]) and checking if the dispatchable is feeless before applying the
|
||||
//! wrapped extension. If the dispatchable is indeed feeless, the extension is skipped and a custom
|
||||
//! event is emitted instead. Otherwise, the extension is applied as usual.
|
||||
//!
|
||||
//!
|
||||
//! ## Integration
|
||||
//!
|
||||
//! This pallet wraps an existing transaction payment pallet. This means you should both pallets
|
||||
//! in your `construct_runtime` macro and include this pallet's
|
||||
//! [`SignedExtension`] ([`SkipCheckIfFeeless`]) that would accept the existing one as an argument.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
dispatch::{CheckIfFeeless, DispatchResult},
|
||||
traits::{IsType, OriginTrait},
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{
|
||||
traits::{DispatchInfoOf, PostDispatchInfoOf, SignedExtension},
|
||||
transaction_validity::TransactionValidityError,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
/// The overarching event type.
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// A transaction fee was skipped.
|
||||
FeeSkipped { who: T::AccountId },
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SignedExtension`] that skips the wrapped extension if the dispatchable is feeless.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct SkipCheckIfFeeless<T: Config, S: SignedExtension>(pub S, sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config, S: SignedExtension> sp_std::fmt::Debug for SkipCheckIfFeeless<T, S> {
|
||||
#[cfg(feature = "std")]
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
write!(f, "SkipCheckIfFeeless<{:?}>", self.0.encode())
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config + Send + Sync, S: SignedExtension> SkipCheckIfFeeless<T, S> {
|
||||
/// utility constructor. Used only in client/factory code.
|
||||
pub fn from(s: S) -> Self {
|
||||
Self(s, sp_std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config + Send + Sync, S: SignedExtension<AccountId = T::AccountId>> SignedExtension
|
||||
for SkipCheckIfFeeless<T, S>
|
||||
where
|
||||
S::Call: CheckIfFeeless<Origin = frame_system::pallet_prelude::OriginFor<T>>,
|
||||
{
|
||||
type AccountId = T::AccountId;
|
||||
type Call = S::Call;
|
||||
type AdditionalSigned = S::AdditionalSigned;
|
||||
type Pre = (Self::AccountId, Option<<S as SignedExtension>::Pre>);
|
||||
const IDENTIFIER: &'static str = "SkipCheckIfFeeless";
|
||||
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
|
||||
self.0.additional_signed()
|
||||
}
|
||||
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
call: &Self::Call,
|
||||
info: &DispatchInfoOf<Self::Call>,
|
||||
len: usize,
|
||||
) -> Result<Self::Pre, TransactionValidityError> {
|
||||
if call.is_feeless(&<T as frame_system::Config>::RuntimeOrigin::signed(who.clone())) {
|
||||
Ok((who.clone(), None))
|
||||
} else {
|
||||
Ok((who.clone(), Some(self.0.pre_dispatch(who, call, info, len)?)))
|
||||
}
|
||||
}
|
||||
|
||||
fn post_dispatch(
|
||||
pre: Option<Self::Pre>,
|
||||
info: &DispatchInfoOf<Self::Call>,
|
||||
post_info: &PostDispatchInfoOf<Self::Call>,
|
||||
len: usize,
|
||||
result: &DispatchResult,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
if let Some(pre) = pre {
|
||||
if let Some(pre) = pre.1 {
|
||||
S::post_dispatch(Some(pre), info, post_info, len, result)?;
|
||||
} else {
|
||||
Pallet::<T>::deposit_event(Event::<T>::FeeSkipped { who: pre.0 });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate as pallet_skip_feeless_payment;
|
||||
|
||||
use frame_support::{derive_impl, parameter_types};
|
||||
use frame_system as system;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
type AccountId = u64;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PreDispatchCount: u32 = 0;
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)]
|
||||
pub struct DummyExtension;
|
||||
|
||||
impl SignedExtension for DummyExtension {
|
||||
type AccountId = AccountId;
|
||||
type Call = RuntimeCall;
|
||||
type AdditionalSigned = ();
|
||||
type Pre = ();
|
||||
const IDENTIFIER: &'static str = "DummyExtension";
|
||||
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
|
||||
Ok(())
|
||||
}
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
_who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
_info: &DispatchInfoOf<Self::Call>,
|
||||
_len: usize,
|
||||
) -> Result<Self::Pre, TransactionValidityError> {
|
||||
PreDispatchCount::mutate(|c| *c += 1);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
pub mod pallet_dummy {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::feeless_if(|_origin: &OriginFor<T>, data: &u32| -> bool {
|
||||
*data == 0
|
||||
})]
|
||||
pub fn aux(_origin: OriginFor<T>, #[pallet::compact] _data: u32) -> DispatchResult {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_dummy::Config for Runtime {}
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub struct Runtime {
|
||||
System: system,
|
||||
SkipFeeless: pallet_skip_feeless_payment,
|
||||
DummyPallet: pallet_dummy,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate::mock::{pallet_dummy::Call, DummyExtension, PreDispatchCount, Runtime, RuntimeCall};
|
||||
use frame_support::dispatch::DispatchInfo;
|
||||
|
||||
#[test]
|
||||
fn skip_feeless_payment_works() {
|
||||
let call = RuntimeCall::DummyPallet(Call::<Runtime>::aux { data: 1 });
|
||||
SkipCheckIfFeeless::<Runtime, DummyExtension>::from(DummyExtension)
|
||||
.pre_dispatch(&0, &call, &DispatchInfo::default(), 0)
|
||||
.unwrap();
|
||||
assert_eq!(PreDispatchCount::get(), 1);
|
||||
|
||||
let call = RuntimeCall::DummyPallet(Call::<Runtime>::aux { data: 0 });
|
||||
SkipCheckIfFeeless::<Runtime, DummyExtension>::from(DummyExtension)
|
||||
.pre_dispatch(&0, &call, &DispatchInfo::default(), 0)
|
||||
.unwrap();
|
||||
assert_eq!(PreDispatchCount::get(), 1);
|
||||
}
|
||||
Reference in New Issue
Block a user