referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/pallet/parse/
call.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use super::{helper, InheritedCallWeightAttr};
19use frame_support_procedural_tools::get_doc_literals;
20use proc_macro2::Span;
21use quote::ToTokens;
22use std::collections::HashMap;
23use syn::spanned::Spanned;
24
25/// List of additional token to be used for parsing.
26mod keyword {
27	syn::custom_keyword!(Call);
28	syn::custom_keyword!(OriginFor);
29	syn::custom_keyword!(RuntimeOrigin);
30	syn::custom_keyword!(weight);
31	syn::custom_keyword!(call_index);
32	syn::custom_keyword!(compact);
33	syn::custom_keyword!(T);
34	syn::custom_keyword!(pallet);
35	syn::custom_keyword!(feeless_if);
36	syn::custom_keyword!(authorize);
37	syn::custom_keyword!(weight_of_authorize);
38}
39
40/// Definition of dispatchables typically `impl<T: Config> Pallet<T> { ... }`
41pub struct CallDef {
42	/// The where_clause used.
43	pub where_clause: Option<syn::WhereClause>,
44	/// A set of usage of instance, must be check for consistency with trait.
45	pub instances: Vec<helper::InstanceUsage>,
46	/// The index of call item in pallet module.
47	pub index: usize,
48	/// Information on methods (used for expansion).
49	pub methods: Vec<CallVariantDef>,
50	/// The span of the pallet::call attribute.
51	pub attr_span: proc_macro2::Span,
52	/// Docs, specified on the impl Block.
53	pub docs: Vec<syn::Expr>,
54}
55
56/// The weight of a call or the weight of authorize.
57#[derive(Clone)]
58pub enum CallWeightDef {
59	/// Explicitly set on the call itself with `#[pallet::weight(…)]` or
60	/// `#[pallet::weight_of_authorize(…)]`. This value is used.
61	Immediate(syn::Expr),
62
63	/// The default value that should be set for dev-mode pallets. Usually zero.
64	DevModeDefault,
65
66	/// Inherits whatever value is configured on the pallet level.
67	///
68	/// The concrete value is not known at this point.
69	Inherited(syn::Type),
70}
71
72impl CallWeightDef {
73	fn try_from(
74		weight: Option<syn::Expr>,
75		inherited_call_weight: &Option<InheritedCallWeightAttr>,
76		dev_mode: bool,
77	) -> Option<Self> {
78		match (weight, inherited_call_weight) {
79			(Some(weight), _) => Some(CallWeightDef::Immediate(weight)),
80			(None, Some(inherited)) => Some(CallWeightDef::Inherited(inherited.typename.clone())),
81			(None, _) if dev_mode => Some(CallWeightDef::DevModeDefault),
82			(None, _) => None,
83		}
84	}
85}
86
87/// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..`
88#[derive(Clone)]
89pub struct CallVariantDef {
90	/// Function name.
91	pub name: syn::Ident,
92	/// Information on args: `(is_compact, name, type)`
93	pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
94	/// Weight for the call.
95	pub weight: CallWeightDef,
96	/// Call index of the dispatchable.
97	pub call_index: u8,
98	/// Whether an explicit call index was specified.
99	pub explicit_call_index: bool,
100	/// Docs, used for metadata.
101	pub docs: Vec<syn::Expr>,
102	/// Attributes annotated at the top of the dispatchable function.
103	pub attrs: Vec<syn::Attribute>,
104	/// The `cfg` attributes.
105	pub cfg_attrs: Vec<syn::Attribute>,
106	/// The optional `feeless_if` attribute on the `pallet::call`.
107	pub feeless_check: Option<syn::ExprClosure>,
108	/// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`.
109	pub return_type: helper::CallReturnType,
110	/// The information related to `authorize` attribute.
111	/// `(authorize expression, weight of authorize)`
112	pub authorize: Option<AuthorizeDef>,
113}
114
115/// Definition related to the `authorize` attribute and other related attributes.
116#[derive(Clone)]
117pub struct AuthorizeDef {
118	/// The expression of the authorize attribute.
119	pub expr: syn::Expr,
120	/// The weight of the authorize attribute as define by the attribute
121	/// `[pallet::weight_of_authorize]`.
122	pub weight: CallWeightDef,
123}
124
125/// Attributes for functions in call impl block.
126pub enum FunctionAttr {
127	/// Parse for `#[pallet::call_index(expr)]`
128	CallIndex(u8),
129	/// Parse for `#[pallet::weight(expr)]`
130	Weight(syn::Expr),
131	/// Parse for `#[pallet::feeless_if(expr)]`
132	FeelessIf(Span, syn::ExprClosure),
133	/// Parse for `#[pallet::authorize(expr)]`
134	Authorize(syn::Expr),
135	/// Parse for `#[pallet::weight_of_authorize(expr)]`
136	WeightOfAuthorize(syn::Expr),
137}
138
139impl syn::parse::Parse for FunctionAttr {
140	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
141		input.parse::<syn::Token![#]>()?;
142		let content;
143		syn::bracketed!(content in input);
144		content.parse::<keyword::pallet>()?;
145		content.parse::<syn::Token![::]>()?;
146
147		let lookahead = content.lookahead1();
148		if lookahead.peek(keyword::weight) {
149			content.parse::<keyword::weight>()?;
150			let weight_content;
151			syn::parenthesized!(weight_content in content);
152			Ok(FunctionAttr::Weight(weight_content.parse::<syn::Expr>()?))
153		} else if lookahead.peek(keyword::call_index) {
154			content.parse::<keyword::call_index>()?;
155			let call_index_content;
156			syn::parenthesized!(call_index_content in content);
157			let index = call_index_content.parse::<syn::LitInt>()?;
158			if !index.suffix().is_empty() {
159				let msg = "Number literal must not have a suffix";
160				return Err(syn::Error::new(index.span(), msg));
161			}
162			Ok(FunctionAttr::CallIndex(index.base10_parse()?))
163		} else if lookahead.peek(keyword::feeless_if) {
164			content.parse::<keyword::feeless_if>()?;
165			let closure_content;
166			syn::parenthesized!(closure_content in content);
167			Ok(FunctionAttr::FeelessIf(
168				closure_content.span(),
169				closure_content.parse::<syn::ExprClosure>().map_err(|e| {
170					let msg = "Invalid feeless_if attribute: expected a closure";
171					let mut err = syn::Error::new(closure_content.span(), msg);
172					err.combine(e);
173					err
174				})?,
175			))
176		} else if lookahead.peek(keyword::authorize) {
177			content.parse::<keyword::authorize>()?;
178			let closure_content;
179			syn::parenthesized!(closure_content in content);
180			Ok(FunctionAttr::Authorize(closure_content.parse::<syn::Expr>()?))
181		} else if lookahead.peek(keyword::weight_of_authorize) {
182			content.parse::<keyword::weight_of_authorize>()?;
183			let closure_content;
184			syn::parenthesized!(closure_content in content);
185			Ok(FunctionAttr::WeightOfAuthorize(closure_content.parse::<syn::Expr>()?))
186		} else {
187			Err(lookahead.error())
188		}
189	}
190}
191
192/// Attribute for arguments in function in call impl block.
193/// Parse for `#[pallet::compact]|
194pub struct ArgAttrIsCompact;
195
196impl syn::parse::Parse for ArgAttrIsCompact {
197	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
198		input.parse::<syn::Token![#]>()?;
199		let content;
200		syn::bracketed!(content in input);
201		content.parse::<keyword::pallet>()?;
202		content.parse::<syn::Token![::]>()?;
203
204		content.parse::<keyword::compact>()?;
205		Ok(ArgAttrIsCompact)
206	}
207}
208
209/// Check the syntax is `OriginFor<T>`, `&OriginFor<T>` or `T::RuntimeOrigin`.
210pub fn check_dispatchable_first_arg_type(ty: &syn::Type, is_ref: bool) -> syn::Result<()> {
211	pub struct CheckOriginFor(bool);
212	impl syn::parse::Parse for CheckOriginFor {
213		fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
214			let is_ref = input.parse::<syn::Token![&]>().is_ok();
215			input.parse::<keyword::OriginFor>()?;
216			input.parse::<syn::Token![<]>()?;
217			input.parse::<keyword::T>()?;
218			input.parse::<syn::Token![>]>()?;
219
220			Ok(Self(is_ref))
221		}
222	}
223
224	pub struct CheckRuntimeOrigin;
225	impl syn::parse::Parse for CheckRuntimeOrigin {
226		fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
227			input.parse::<keyword::T>()?;
228			input.parse::<syn::Token![::]>()?;
229			input.parse::<keyword::RuntimeOrigin>()?;
230
231			Ok(Self)
232		}
233	}
234
235	let result_origin_for = syn::parse2::<CheckOriginFor>(ty.to_token_stream());
236	let result_runtime_origin = syn::parse2::<CheckRuntimeOrigin>(ty.to_token_stream());
237	return match (result_origin_for, result_runtime_origin) {
238		(Ok(CheckOriginFor(has_ref)), _) if is_ref == has_ref => Ok(()),
239		(_, Ok(_)) => Ok(()),
240		(_, _) => {
241			let msg = if is_ref {
242				"Invalid type: expected `&OriginFor<T>`"
243			} else {
244				"Invalid type: expected `OriginFor<T>` or `T::RuntimeOrigin`"
245			};
246			return Err(syn::Error::new(ty.span(), msg));
247		},
248	};
249}
250
251impl CallDef {
252	pub fn try_from(
253		attr_span: proc_macro2::Span,
254		index: usize,
255		item: &mut syn::Item,
256		dev_mode: bool,
257		inherited_call_weight: Option<InheritedCallWeightAttr>,
258	) -> syn::Result<Self> {
259		let item_impl = if let syn::Item::Impl(item) = item {
260			item
261		} else {
262			return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl"));
263		};
264
265		crate::deprecation::prevent_deprecation_attr_on_outer_enum(&item_impl.attrs)?;
266
267		let instances = vec![
268			helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?,
269			helper::check_pallet_struct_usage(&item_impl.self_ty)?,
270		];
271
272		if let Some((_, _, for_)) = item_impl.trait_ {
273			let msg = "Invalid pallet::call, expected no trait ident as in \
274				`impl<..> Pallet<..> { .. }`";
275			return Err(syn::Error::new(for_.span(), msg));
276		}
277
278		let mut methods = vec![];
279		let mut indices = HashMap::new();
280		let mut last_index: Option<u8> = None;
281		for item in &mut item_impl.items {
282			if let syn::ImplItem::Fn(method) = item {
283				if !matches!(method.vis, syn::Visibility::Public(_)) {
284					let msg = "Invalid pallet::call, dispatchable function must be public: \
285						`pub fn`";
286
287					let span = match method.vis {
288						syn::Visibility::Inherited => method.sig.span(),
289						_ => method.vis.span(),
290					};
291
292					return Err(syn::Error::new(span, msg));
293				}
294
295				match method.sig.inputs.first() {
296					None => {
297						let msg = "Invalid pallet::call, must have at least origin arg";
298						return Err(syn::Error::new(method.sig.span(), msg));
299					},
300					Some(syn::FnArg::Receiver(_)) => {
301						let msg = "Invalid pallet::call, first argument must be a typed argument, \
302							e.g. `origin: OriginFor<T>`";
303						return Err(syn::Error::new(method.sig.span(), msg));
304					},
305					Some(syn::FnArg::Typed(arg)) => {
306						check_dispatchable_first_arg_type(&arg.ty, false)?;
307					},
308				}
309
310				let return_type = helper::check_pallet_call_return_type(&method.sig)?;
311
312				let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&method.attrs);
313				let mut call_index = None;
314				let mut weight = None;
315				let mut feeless_check = None;
316				let mut authorize = None;
317				let mut weight_of_authorize = None;
318
319				for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() {
320					match attr {
321						FunctionAttr::CallIndex(idx) => {
322							if call_index.is_some() {
323								let msg =
324									"Invalid pallet::call, too many call_index attributes given";
325								return Err(syn::Error::new(method.sig.span(), msg))
326							}
327
328							call_index = Some(idx);
329						},
330						FunctionAttr::Weight(w) => {
331							if weight.is_some() {
332								let msg = "Invalid pallet::call, too many weight attributes given";
333								return Err(syn::Error::new(method.sig.span(), msg))
334							}
335							weight = Some(w);
336						},
337						FunctionAttr::FeelessIf(span, closure) => {
338							if feeless_check.is_some() {
339								let msg =
340									"Invalid pallet::call, there can only be one feeless_if attribute";
341								return Err(syn::Error::new(span, msg))
342							}
343
344							feeless_check = Some(closure);
345						},
346						FunctionAttr::Authorize(expr) => {
347							if authorize.is_some() {
348								let msg =
349									"Invalid pallet::call, there can only be one authorize attribute";
350								return Err(syn::Error::new(method.sig.span(), msg))
351							}
352
353							authorize = Some(expr);
354						},
355						FunctionAttr::WeightOfAuthorize(expr) => {
356							if weight_of_authorize.is_some() {
357								let msg = "Invalid pallet::call, there can only be one weight_of_authorize attribute";
358								return Err(syn::Error::new(method.sig.span(), msg))
359							}
360
361							weight_of_authorize = Some(expr);
362						},
363					}
364				}
365
366				if weight_of_authorize.is_some() && authorize.is_none() {
367					let msg = "Invalid pallet::call, weight_of_authorize attribute must be used with authorize attribute";
368					return Err(syn::Error::new(weight_of_authorize.unwrap().span(), msg))
369				}
370
371				let authorize = if let Some(expr) = authorize {
372					let weight_of_authorize = CallWeightDef::try_from(
373						weight_of_authorize,
374						&inherited_call_weight,
375						dev_mode,
376					)
377					.ok_or_else(|| {
378						syn::Error::new(
379							method.sig.span(),
380							"A pallet::call using authorize requires either a concrete \
381							`#[pallet::weight_of_authorize($expr)]` or an inherited weight from \
382							the `#[pallet:call(weight($type))]` attribute, but \
383							none were given.",
384						)
385					})?;
386					Some(AuthorizeDef { expr, weight: weight_of_authorize })
387				} else {
388					None
389				};
390
391				let weight = CallWeightDef::try_from(weight, &inherited_call_weight, dev_mode)
392					.ok_or_else(|| {
393						syn::Error::new(
394							method.sig.span(),
395							"A pallet::call requires either a concrete `#[pallet::weight($expr)]` \
396							or an inherited weight from the `#[pallet:call(weight($type))]` \
397							attribute, but none were given.",
398						)
399					})?;
400
401				let explicit_call_index = call_index.is_some();
402
403				let final_index = match call_index {
404					Some(i) => i,
405					None =>
406						last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| {
407							let msg = "Call index doesn't fit into u8, index is 256";
408							syn::Error::new(method.sig.span(), msg)
409						})?,
410				};
411				last_index = Some(final_index);
412
413				if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) {
414					let msg = format!(
415						"Call indices are conflicting: Both functions {} and {} are at index {}",
416						used_fn, method.sig.ident, final_index,
417					);
418					let mut err = syn::Error::new(used_fn.span(), &msg);
419					err.combine(syn::Error::new(method.sig.ident.span(), msg));
420					return Err(err);
421				}
422
423				let mut args = vec![];
424				for arg in method.sig.inputs.iter_mut().skip(1) {
425					let arg = if let syn::FnArg::Typed(arg) = arg {
426						arg
427					} else {
428						unreachable!("Only first argument can be receiver");
429					};
430
431					let arg_attrs: Vec<ArgAttrIsCompact> =
432						helper::take_item_pallet_attrs(&mut arg.attrs)?;
433
434					if arg_attrs.len() > 1 {
435						let msg = "Invalid pallet::call, argument has too many attributes";
436						return Err(syn::Error::new(arg.span(), msg));
437					}
438
439					let arg_ident = if let syn::Pat::Ident(pat) = &*arg.pat {
440						pat.ident.clone()
441					} else {
442						let msg = "Invalid pallet::call, argument must be ident";
443						return Err(syn::Error::new(arg.pat.span(), msg));
444					};
445
446					args.push((!arg_attrs.is_empty(), arg_ident, arg.ty.clone()));
447				}
448
449				let docs = get_doc_literals(&method.attrs);
450
451				if let Some(ref feeless_check) = feeless_check {
452					if feeless_check.inputs.len() != args.len() + 1 {
453						let msg = "Invalid pallet::call, feeless_if closure must have same \
454							number of arguments as the dispatchable function";
455						return Err(syn::Error::new(feeless_check.span(), msg));
456					}
457
458					match feeless_check.inputs.first() {
459						None => {
460							let msg = "Invalid pallet::call, feeless_if closure must have at least origin arg";
461							return Err(syn::Error::new(feeless_check.span(), msg));
462						},
463						Some(syn::Pat::Type(arg)) => {
464							check_dispatchable_first_arg_type(&arg.ty, true)?;
465						},
466						_ => {
467							let msg = "Invalid pallet::call, feeless_if closure first argument must be a typed argument, \
468								e.g. `origin: OriginFor<T>`";
469							return Err(syn::Error::new(feeless_check.span(), msg));
470						},
471					}
472
473					for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) {
474						let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) =
475							feeless_arg.clone()
476						{
477							if let syn::Type::Reference(pat) = *ty {
478								pat.elem.clone()
479							} else {
480								let msg = "Invalid pallet::call, feeless_if closure argument must be a reference";
481								return Err(syn::Error::new(ty.span(), msg));
482							}
483						} else {
484							let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern";
485							return Err(syn::Error::new(feeless_arg.span(), msg));
486						};
487
488						if feeless_arg_type != arg.2 {
489							let msg =
490								"Invalid pallet::call, feeless_if closure argument must have \
491								a reference to the same type as the dispatchable function argument";
492							return Err(syn::Error::new(feeless_arg.span(), msg));
493						}
494					}
495
496					let valid_return = match &feeless_check.output {
497						syn::ReturnType::Type(_, type_) => match *(type_.clone()) {
498							syn::Type::Path(syn::TypePath { path, .. }) => path.is_ident("bool"),
499							_ => false,
500						},
501						_ => false,
502					};
503					if !valid_return {
504						let msg = "Invalid pallet::call, feeless_if closure must return `bool`";
505						return Err(syn::Error::new(feeless_check.output.span(), msg));
506					}
507				}
508
509				methods.push(CallVariantDef {
510					name: method.sig.ident.clone(),
511					weight,
512					call_index: final_index,
513					explicit_call_index,
514					args,
515					docs,
516					attrs: method.attrs.clone(),
517					cfg_attrs,
518					feeless_check,
519					return_type,
520					authorize,
521				});
522			} else {
523				let msg = "Invalid pallet::call, only method accepted";
524				return Err(syn::Error::new(item.span(), msg));
525			}
526		}
527
528		Ok(Self {
529			index,
530			attr_span,
531			instances,
532			methods,
533			where_clause: item_impl.generics.where_clause.clone(),
534			docs: get_doc_literals(&item_impl.attrs),
535		})
536	}
537}