Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds syntax for marking calls feeless #1926

Merged
merged 49 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
61695cf
Initial setup
gupnik Sep 27, 2023
200abe3
Merge branch 'master' of github.com:paritytech/polkadot-sdk into gupn…
Oct 11, 2023
d199795
Fixes end-to-end
gupnik Oct 18, 2023
b0390bf
Adds pallet-skip-feeless-payment
gupnik Oct 20, 2023
aac1933
Merge branch 'master' into gupnik/feeless
gupnik Oct 20, 2023
7e0450b
Adds AccountIdFor in test
gupnik Oct 20, 2023
6802f4d
Fixes node build
gupnik Oct 20, 2023
093fa63
Fixes prelude in tests
gupnik Oct 20, 2023
9cf7401
Minor fixes
gupnik Oct 20, 2023
25f57f5
Fixes feature propagation
gupnik Oct 20, 2023
de68ccc
Fixes feature propagation
gupnik Oct 20, 2023
9907ca5
Merge branch 'master' into gupnik/feeless
gupnik Oct 20, 2023
fac68c1
Formats features
gupnik Oct 21, 2023
b0fdbc1
Updates UI test
gupnik Oct 21, 2023
32e9bd1
Merge branch 'master' into gupnik/feeless
gupnik Oct 21, 2023
73c7bca
Adds tests for skip fee payment pallet
gupnik Oct 23, 2023
84bb8fa
Adds UI Tests for feeless args
gupnik Oct 24, 2023
948207a
Adds UI Tests for feeless input type
gupnik Oct 24, 2023
be7a0fc
Adds UI Tests for feeless return type
gupnik Oct 24, 2023
23cfd91
Adds UI Test for passing case
gupnik Oct 24, 2023
e87a139
Moves feeless extension to kitchensink
gupnik Oct 25, 2023
1c95fdf
Fixes node-testing build
gupnik Oct 25, 2023
049ed24
Merge branch 'master' into gupnik/feeless
gupnik Oct 25, 2023
ff860e6
Fixes node-cli build
gupnik Oct 25, 2023
a9074a5
Adds feeless macro in kitchensink example
gupnik Oct 25, 2023
6706bc2
Fmt
gupnik Oct 25, 2023
edaf143
fixes build
gupnik Oct 25, 2023
9987b88
Adds docs
gupnik Oct 25, 2023
864b50c
Adds prdoc
gupnik Oct 25, 2023
646fe0f
Minor fix
gupnik Oct 25, 2023
5049e94
Update substrate/frame/transaction-payment/skip-feeless-payment/Cargo…
gupnik Nov 3, 2023
649708a
Update substrate/frame/transaction-payment/skip-feeless-payment/Cargo…
gupnik Nov 3, 2023
b12fa28
Update substrate/frame/transaction-payment/skip-feeless-payment/Cargo…
gupnik Nov 3, 2023
c8f99a6
Addresses review comments
gupnik Nov 7, 2023
615558e
Merge branch 'master' of github.com:paritytech/polkadot-sdk into gupn…
gupnik Nov 7, 2023
c11a5e8
Uses Origin as the first arg
gupnik Nov 7, 2023
ff51a94
Update substrate/frame/support/src/dispatch.rs
gupnik Nov 11, 2023
a1e2ea9
Addresses review comments
gupnik Nov 11, 2023
c6f1091
Update substrate/frame/support/procedural/src/lib.rs
gupnik Nov 11, 2023
69193c6
Update substrate/frame/support/procedural/src/pallet/parse/call.rs
gupnik Nov 11, 2023
5a44ab4
Update prdoc/pr_1926.prdoc
gupnik Nov 11, 2023
90f2cf8
Update substrate/frame/support/procedural/src/lib.rs
gupnik Nov 11, 2023
f031785
Update substrate/frame/support/procedural/src/lib.rs
gupnik Nov 11, 2023
e210941
Addresses review comments
gupnik Nov 11, 2023
d79cc22
Fixes error description for invalid attr type
gupnik Nov 11, 2023
0ab5492
Fixes error description and span for closure args
gupnik Nov 11, 2023
e8e2f70
Merge branch 'master' into gupnik/feeless
gupnik Nov 11, 2023
3f9faad
Updates error output in UI test
gupnik Nov 11, 2023
9351d4b
Merge branch 'master' into gupnik/feeless
gupnik Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions substrate/bin/node-template/pallets/template/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ pub mod pallet {
/// error if it isn't. Learn more about origins here: <https://docs.substrate.io/build/origins/>
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::do_something())]
#[pallet::feeless_if(|_who: &AccountIdFor<T>, something: &u32| -> bool {
*something == 0
})]
pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
// Check that the extrinsic was signed and get the signer.
let who = ensure_signed(origin)?;
Expand Down
2 changes: 1 addition & 1 deletion substrate/bin/node-template/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub type SignedExtra = (
frame_system::CheckEra<Runtime>,
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
SkipCheckIfFeeless<pallet_transaction_payment::ChargeTransactionPayment<Runtime>>,
);

/// Unchecked extrinsic type as expected by this runtime.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ pub fn expand_outer_dispatch(
}
}

impl #scrate::dispatch::CheckIfFeeless for RuntimeCall {
type AccountId = #system_path::pallet_prelude::AccountIdFor<#runtime>;
fn is_feeless(&self, account_id: &Self::AccountId) -> bool {
match self {
#(
#pallet_attrs
#variant_patterns => call.is_feeless(account_id),
)*
}
}
}

impl #scrate::traits::GetCallMetadata for RuntimeCall {
fn get_call_metadata(&self) -> #scrate::traits::CallMetadata {
use #scrate::traits::GetCallName;
Expand Down Expand Up @@ -207,5 +219,61 @@ pub fn expand_outer_dispatch(
}
}
)*

/// SkipCheckIfFeeless
#[derive(
#scrate::RuntimeDebugNoBound,
#scrate::CloneNoBound,
#scrate::EqNoBound,
#scrate::PartialEqNoBound,
#scrate::__private::codec::Encode,
#scrate::__private::codec::Decode,
#scrate::__private::scale_info::TypeInfo,
)]
pub struct SkipCheckIfFeeless<T: #scrate::sp_runtime::traits::SignedExtension>(T);
gupnik marked this conversation as resolved.
Show resolved Hide resolved

impl<T: #scrate::sp_runtime::traits::SignedExtension> #scrate::sp_runtime::traits::SignedExtension for SkipCheckIfFeeless<T>
where
T::Call: #scrate::dispatch::CheckIfFeeless<AccountId = T::AccountId>
{
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = ();
type Pre = Option<T::Pre>;
const IDENTIFIER: &'static str = "SkipCheckIfFeeless";

fn additional_signed(&self) -> #scrate::__private::sp_std::result::Result<(), #scrate::sp_runtime::transaction_validity::TransactionValidityError> {
Ok(())
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &#scrate::sp_runtime::traits::DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, #scrate::sp_runtime::transaction_validity::TransactionValidityError> {
use #scrate::dispatch::CheckIfFeeless;
match call.is_feeless(who) {
gupnik marked this conversation as resolved.
Show resolved Hide resolved
true => Ok(None),
false => Ok(Some(self.0.pre_dispatch(who, call, info, len)?)),
}
}

fn post_dispatch(
pre: Option<Self::Pre>,
info: &#scrate::sp_runtime::traits::DispatchInfoOf<Self::Call>,
post_info: &#scrate::sp_runtime::traits::PostDispatchInfoOf<Self::Call>,
len: usize,
result: &#scrate::dispatch::DispatchResult,
) -> Result<(), #scrate::sp_runtime::transaction_validity::TransactionValidityError> {
if let Some(pre) = pre {
if let Some(pre) = pre {
T::post_dispatch(Some(pre), info, post_info, len, result)?;
}
}
Ok(())
}
}
}
}
27 changes: 27 additions & 0 deletions substrate/frame/support/procedural/src/pallet/expand/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(account_id, #( #arg_name, )*))
} else {
quote::quote!(false)
}
});

quote::quote_spanned!(span =>
mod warnings {
#(
Expand Down Expand Up @@ -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 AccountId = #frame_system::pallet_prelude::AccountIdFor<T>;
#[allow(unused_variables)]
fn is_feeless(&self, account_id: &Self::AccountId) -> 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
{
Expand Down
49 changes: 38 additions & 11 deletions substrate/frame/support/procedural/src/pallet/parse/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::{helper, InheritedCallWeightAttr};
use frame_support_procedural_tools::get_doc_literals;
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 {
Expand All @@ -30,6 +30,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> { ... }`
Expand Down Expand Up @@ -82,13 +83,17 @@ 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)]
/// Parse for `#[pallet::weight(expr)]` or `#[pallet::call_index(expr)] or
/// `#[pallet::feeless_if(expr)]`
gupnik marked this conversation as resolved.
Show resolved Hide resolved
pub enum FunctionAttr {
CallIndex(u8),
Weight(syn::Expr),
FeelessIf(syn::ExprClosure),
}

impl syn::parse::Parse for FunctionAttr {
Expand All @@ -115,6 +120,11 @@ 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.parse::<syn::ExprClosure>()?))
} else {
Err(lookahead.error())
}
Expand Down Expand Up @@ -227,16 +237,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(_) => {
feeless_attrs.push(attr);
},
}
}

if weight_attrs.is_empty() && dev_mode {
// inject a default O(1) weight when dev mode is enabled and no weight has
Expand Down Expand Up @@ -323,6 +339,16 @@ impl CallDef {

let docs = get_doc_literals(&method.attrs);

if feeless_attrs.len() > 1 {
let msg = "Invalid pallet::call, too many feeless_if attributes given";
gupnik marked this conversation as resolved.
Show resolved Hide resolved
return Err(syn::Error::new(method.sig.span(), msg))
gupnik marked this conversation as resolved.
Show resolved Hide resolved
}
let feeless_check: Option<ExprClosure> =
feeless_attrs.pop().map(|attr| match attr {
FunctionAttr::FeelessIf(closure) => closure,
_ => unreachable!("checked during creation of the let binding"),
gupnik marked this conversation as resolved.
Show resolved Hide resolved
});

methods.push(CallVariantDef {
name: method.sig.ident.clone(),
weight,
Expand All @@ -331,6 +357,7 @@ impl CallDef {
args,
docs,
attrs: method.attrs.clone(),
feeless_check,
});
} else {
let msg = "Invalid pallet::call, only method accepted";
Expand Down
6 changes: 6 additions & 0 deletions substrate/frame/support/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ pub trait Callable<T> {
// https://github.com/rust-lang/rust/issues/51331
pub type CallableCallFor<A, R> = <A as Callable<R>>::RuntimeCall;

pub trait CheckIfFeeless {
type AccountId;

fn is_feeless(&self, account_d: &Self::AccountId) -> bool;
}

/// Origin for the System pallet.
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum RawOrigin<AccountId> {
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,8 @@ pub mod pallet_prelude {
/// Type alias for the `Origin` associated type of system config.
pub type OriginFor<T> = <T as crate::Config>::RuntimeOrigin;

pub type AccountIdFor<T> = <T as crate::Config>::AccountId;

/// Type alias for the `Header`.
pub type HeaderFor<T> =
<<T as crate::Config>::Block as sp_runtime::traits::HeaderProvider>::HeaderT;
Expand Down
Loading