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, ExprClosure};
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}
37
38/// Definition of dispatchables typically `impl<T: Config> Pallet<T> { ... }`
39pub struct CallDef {
40	/// The where_clause used.
41	pub where_clause: Option<syn::WhereClause>,
42	/// A set of usage of instance, must be check for consistency with trait.
43	pub instances: Vec<helper::InstanceUsage>,
44	/// The index of call item in pallet module.
45	pub index: usize,
46	/// Information on methods (used for expansion).
47	pub methods: Vec<CallVariantDef>,
48	/// The span of the pallet::call attribute.
49	pub attr_span: proc_macro2::Span,
50	/// Docs, specified on the impl Block.
51	pub docs: Vec<syn::Expr>,
52	/// The optional `weight` attribute on the `pallet::call`.
53	pub inherited_call_weight: Option<InheritedCallWeightAttr>,
54	/// attributes
55	pub attrs: Vec<syn::Attribute>,
56}
57
58/// The weight of a call.
59#[derive(Clone)]
60pub enum CallWeightDef {
61	/// Explicitly set on the call itself with `#[pallet::weight(…)]`. This value is used.
62	Immediate(syn::Expr),
63
64	/// The default value that should be set for dev-mode pallets. Usually zero.
65	DevModeDefault,
66
67	/// Inherits whatever value is configured on the pallet level.
68	///
69	/// The concrete value is not known at this point.
70	Inherited,
71}
72
73/// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..`
74#[derive(Clone)]
75pub struct CallVariantDef {
76	/// Function name.
77	pub name: syn::Ident,
78	/// Information on args: `(is_compact, name, type)`
79	pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
80	/// Weight for the call.
81	pub weight: CallWeightDef,
82	/// Call index of the dispatchable.
83	pub call_index: u8,
84	/// Whether an explicit call index was specified.
85	pub explicit_call_index: bool,
86	/// Docs, used for metadata.
87	pub docs: Vec<syn::Expr>,
88	/// Attributes annotated at the top of the dispatchable function.
89	pub attrs: Vec<syn::Attribute>,
90	/// The `cfg` attributes.
91	pub cfg_attrs: Vec<syn::Attribute>,
92	/// The optional `feeless_if` attribute on the `pallet::call`.
93	pub feeless_check: Option<syn::ExprClosure>,
94	/// The return type of the call: `DispatchInfo` or `DispatchResultWithPostInfo`.
95	pub return_type: helper::CallReturnType,
96}
97
98/// Attributes for functions in call impl block.
99pub enum FunctionAttr {
100	/// Parse for `#[pallet::call_index(expr)]`
101	CallIndex(u8),
102	/// Parse for `#[pallet::weight(expr)]`
103	Weight(syn::Expr),
104	/// Parse for `#[pallet::feeless_if(expr)]`
105	FeelessIf(Span, syn::ExprClosure),
106}
107
108impl syn::parse::Parse for FunctionAttr {
109	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
110		input.parse::<syn::Token![#]>()?;
111		let content;
112		syn::bracketed!(content in input);
113		content.parse::<keyword::pallet>()?;
114		content.parse::<syn::Token![::]>()?;
115
116		let lookahead = content.lookahead1();
117		if lookahead.peek(keyword::weight) {
118			content.parse::<keyword::weight>()?;
119			let weight_content;
120			syn::parenthesized!(weight_content in content);
121			Ok(FunctionAttr::Weight(weight_content.parse::<syn::Expr>()?))
122		} else if lookahead.peek(keyword::call_index) {
123			content.parse::<keyword::call_index>()?;
124			let call_index_content;
125			syn::parenthesized!(call_index_content in content);
126			let index = call_index_content.parse::<syn::LitInt>()?;
127			if !index.suffix().is_empty() {
128				let msg = "Number literal must not have a suffix";
129				return Err(syn::Error::new(index.span(), msg));
130			}
131			Ok(FunctionAttr::CallIndex(index.base10_parse()?))
132		} else if lookahead.peek(keyword::feeless_if) {
133			content.parse::<keyword::feeless_if>()?;
134			let closure_content;
135			syn::parenthesized!(closure_content in content);
136			Ok(FunctionAttr::FeelessIf(
137				closure_content.span(),
138				closure_content.parse::<syn::ExprClosure>().map_err(|e| {
139					let msg = "Invalid feeless_if attribute: expected a closure";
140					let mut err = syn::Error::new(closure_content.span(), msg);
141					err.combine(e);
142					err
143				})?,
144			))
145		} else {
146			Err(lookahead.error())
147		}
148	}
149}
150
151/// Attribute for arguments in function in call impl block.
152/// Parse for `#[pallet::compact]|
153pub struct ArgAttrIsCompact;
154
155impl syn::parse::Parse for ArgAttrIsCompact {
156	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
157		input.parse::<syn::Token![#]>()?;
158		let content;
159		syn::bracketed!(content in input);
160		content.parse::<keyword::pallet>()?;
161		content.parse::<syn::Token![::]>()?;
162
163		content.parse::<keyword::compact>()?;
164		Ok(ArgAttrIsCompact)
165	}
166}
167
168/// Check the syntax is `OriginFor<T>`, `&OriginFor<T>` or `T::RuntimeOrigin`.
169pub fn check_dispatchable_first_arg_type(ty: &syn::Type, is_ref: bool) -> syn::Result<()> {
170	pub struct CheckOriginFor(bool);
171	impl syn::parse::Parse for CheckOriginFor {
172		fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
173			let is_ref = input.parse::<syn::Token![&]>().is_ok();
174			input.parse::<keyword::OriginFor>()?;
175			input.parse::<syn::Token![<]>()?;
176			input.parse::<keyword::T>()?;
177			input.parse::<syn::Token![>]>()?;
178
179			Ok(Self(is_ref))
180		}
181	}
182
183	pub struct CheckRuntimeOrigin;
184	impl syn::parse::Parse for CheckRuntimeOrigin {
185		fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
186			input.parse::<keyword::T>()?;
187			input.parse::<syn::Token![::]>()?;
188			input.parse::<keyword::RuntimeOrigin>()?;
189
190			Ok(Self)
191		}
192	}
193
194	let result_origin_for = syn::parse2::<CheckOriginFor>(ty.to_token_stream());
195	let result_runtime_origin = syn::parse2::<CheckRuntimeOrigin>(ty.to_token_stream());
196	return match (result_origin_for, result_runtime_origin) {
197		(Ok(CheckOriginFor(has_ref)), _) if is_ref == has_ref => Ok(()),
198		(_, Ok(_)) => Ok(()),
199		(_, _) => {
200			let msg = if is_ref {
201				"Invalid type: expected `&OriginFor<T>`"
202			} else {
203				"Invalid type: expected `OriginFor<T>` or `T::RuntimeOrigin`"
204			};
205			return Err(syn::Error::new(ty.span(), msg));
206		},
207	};
208}
209
210impl CallDef {
211	pub fn try_from(
212		attr_span: proc_macro2::Span,
213		index: usize,
214		item: &mut syn::Item,
215		dev_mode: bool,
216		inherited_call_weight: Option<InheritedCallWeightAttr>,
217	) -> syn::Result<Self> {
218		let item_impl = if let syn::Item::Impl(item) = item {
219			item
220		} else {
221			return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl"));
222		};
223		let instances = vec![
224			helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?,
225			helper::check_pallet_struct_usage(&item_impl.self_ty)?,
226		];
227
228		if let Some((_, _, for_)) = item_impl.trait_ {
229			let msg = "Invalid pallet::call, expected no trait ident as in \
230				`impl<..> Pallet<..> { .. }`";
231			return Err(syn::Error::new(for_.span(), msg));
232		}
233
234		let mut methods = vec![];
235		let mut indices = HashMap::new();
236		let mut last_index: Option<u8> = None;
237		for item in &mut item_impl.items {
238			if let syn::ImplItem::Fn(method) = item {
239				if !matches!(method.vis, syn::Visibility::Public(_)) {
240					let msg = "Invalid pallet::call, dispatchable function must be public: \
241						`pub fn`";
242
243					let span = match method.vis {
244						syn::Visibility::Inherited => method.sig.span(),
245						_ => method.vis.span(),
246					};
247
248					return Err(syn::Error::new(span, msg));
249				}
250
251				match method.sig.inputs.first() {
252					None => {
253						let msg = "Invalid pallet::call, must have at least origin arg";
254						return Err(syn::Error::new(method.sig.span(), msg));
255					},
256					Some(syn::FnArg::Receiver(_)) => {
257						let msg = "Invalid pallet::call, first argument must be a typed argument, \
258							e.g. `origin: OriginFor<T>`";
259						return Err(syn::Error::new(method.sig.span(), msg));
260					},
261					Some(syn::FnArg::Typed(arg)) => {
262						check_dispatchable_first_arg_type(&arg.ty, false)?;
263					},
264				}
265
266				let return_type = helper::check_pallet_call_return_type(&method.sig)?;
267
268				let cfg_attrs: Vec<syn::Attribute> = helper::get_item_cfg_attrs(&method.attrs);
269				let mut call_idx_attrs = vec![];
270				let mut weight_attrs = vec![];
271				let mut feeless_attrs = vec![];
272				for attr in helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter() {
273					match attr {
274						FunctionAttr::CallIndex(_) => {
275							call_idx_attrs.push(attr);
276						},
277						FunctionAttr::Weight(_) => {
278							weight_attrs.push(attr);
279						},
280						FunctionAttr::FeelessIf(span, _) => {
281							feeless_attrs.push((span, attr));
282						},
283					}
284				}
285
286				if weight_attrs.is_empty() && dev_mode {
287					// inject a default O(1) weight when dev mode is enabled and no weight has
288					// been specified on the call
289					let empty_weight: syn::Expr = syn::parse_quote!(0);
290					weight_attrs.push(FunctionAttr::Weight(empty_weight));
291				}
292
293				let weight = match weight_attrs.len() {
294					0 if inherited_call_weight.is_some() => CallWeightDef::Inherited,
295					0 if dev_mode => CallWeightDef::DevModeDefault,
296					0 => return Err(syn::Error::new(
297						method.sig.span(),
298						"A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an
299						inherited weight from the `#[pallet:call(weight($type))]` attribute, but
300						none were given.",
301					)),
302					1 => match weight_attrs.pop().unwrap() {
303						FunctionAttr::Weight(w) => CallWeightDef::Immediate(w),
304						_ => unreachable!("checked during creation of the let binding"),
305					},
306					_ => {
307						let msg = "Invalid pallet::call, too many weight attributes given";
308						return Err(syn::Error::new(method.sig.span(), msg));
309					},
310				};
311
312				if call_idx_attrs.len() > 1 {
313					let msg = "Invalid pallet::call, too many call_index attributes given";
314					return Err(syn::Error::new(method.sig.span(), msg));
315				}
316				let call_index = call_idx_attrs.pop().map(|attr| match attr {
317					FunctionAttr::CallIndex(idx) => idx,
318					_ => unreachable!("checked during creation of the let binding"),
319				});
320				let explicit_call_index = call_index.is_some();
321
322				let final_index = match call_index {
323					Some(i) => i,
324					None =>
325						last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| {
326							let msg = "Call index doesn't fit into u8, index is 256";
327							syn::Error::new(method.sig.span(), msg)
328						})?,
329				};
330				last_index = Some(final_index);
331
332				if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) {
333					let msg = format!(
334						"Call indices are conflicting: Both functions {} and {} are at index {}",
335						used_fn, method.sig.ident, final_index,
336					);
337					let mut err = syn::Error::new(used_fn.span(), &msg);
338					err.combine(syn::Error::new(method.sig.ident.span(), msg));
339					return Err(err);
340				}
341
342				let mut args = vec![];
343				for arg in method.sig.inputs.iter_mut().skip(1) {
344					let arg = if let syn::FnArg::Typed(arg) = arg {
345						arg
346					} else {
347						unreachable!("Only first argument can be receiver");
348					};
349
350					let arg_attrs: Vec<ArgAttrIsCompact> =
351						helper::take_item_pallet_attrs(&mut arg.attrs)?;
352
353					if arg_attrs.len() > 1 {
354						let msg = "Invalid pallet::call, argument has too many attributes";
355						return Err(syn::Error::new(arg.span(), msg));
356					}
357
358					let arg_ident = if let syn::Pat::Ident(pat) = &*arg.pat {
359						pat.ident.clone()
360					} else {
361						let msg = "Invalid pallet::call, argument must be ident";
362						return Err(syn::Error::new(arg.pat.span(), msg));
363					};
364
365					args.push((!arg_attrs.is_empty(), arg_ident, arg.ty.clone()));
366				}
367
368				let docs = get_doc_literals(&method.attrs);
369
370				if feeless_attrs.len() > 1 {
371					let msg = "Invalid pallet::call, there can only be one feeless_if attribute";
372					return Err(syn::Error::new(feeless_attrs[1].0, msg));
373				}
374				let feeless_check: Option<ExprClosure> =
375					feeless_attrs.pop().map(|(_, attr)| match attr {
376						FunctionAttr::FeelessIf(_, closure) => closure,
377						_ => unreachable!("checked during creation of the let binding"),
378					});
379
380				if let Some(ref feeless_check) = feeless_check {
381					if feeless_check.inputs.len() != args.len() + 1 {
382						let msg = "Invalid pallet::call, feeless_if closure must have same \
383							number of arguments as the dispatchable function";
384						return Err(syn::Error::new(feeless_check.span(), msg));
385					}
386
387					match feeless_check.inputs.first() {
388						None => {
389							let msg = "Invalid pallet::call, feeless_if closure must have at least origin arg";
390							return Err(syn::Error::new(feeless_check.span(), msg));
391						},
392						Some(syn::Pat::Type(arg)) => {
393							check_dispatchable_first_arg_type(&arg.ty, true)?;
394						},
395						_ => {
396							let msg = "Invalid pallet::call, feeless_if closure first argument must be a typed argument, \
397								e.g. `origin: OriginFor<T>`";
398							return Err(syn::Error::new(feeless_check.span(), msg));
399						},
400					}
401
402					for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) {
403						let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) =
404							feeless_arg.clone()
405						{
406							if let syn::Type::Reference(pat) = *ty {
407								pat.elem.clone()
408							} else {
409								let msg = "Invalid pallet::call, feeless_if closure argument must be a reference";
410								return Err(syn::Error::new(ty.span(), msg));
411							}
412						} else {
413							let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern";
414							return Err(syn::Error::new(feeless_arg.span(), msg));
415						};
416
417						if feeless_arg_type != arg.2 {
418							let msg =
419								"Invalid pallet::call, feeless_if closure argument must have \
420								a reference to the same type as the dispatchable function argument";
421							return Err(syn::Error::new(feeless_arg.span(), msg));
422						}
423					}
424
425					let valid_return = match &feeless_check.output {
426						syn::ReturnType::Type(_, type_) => match *(type_.clone()) {
427							syn::Type::Path(syn::TypePath { path, .. }) => path.is_ident("bool"),
428							_ => false,
429						},
430						_ => false,
431					};
432					if !valid_return {
433						let msg = "Invalid pallet::call, feeless_if closure must return `bool`";
434						return Err(syn::Error::new(feeless_check.output.span(), msg));
435					}
436				}
437
438				methods.push(CallVariantDef {
439					name: method.sig.ident.clone(),
440					weight,
441					call_index: final_index,
442					explicit_call_index,
443					args,
444					docs,
445					attrs: method.attrs.clone(),
446					cfg_attrs,
447					feeless_check,
448					return_type,
449				});
450			} else {
451				let msg = "Invalid pallet::call, only method accepted";
452				return Err(syn::Error::new(item.span(), msg));
453			}
454		}
455
456		Ok(Self {
457			index,
458			attr_span,
459			instances,
460			methods,
461			where_clause: item_impl.generics.where_clause.clone(),
462			docs: get_doc_literals(&item_impl.attrs),
463			inherited_call_weight,
464			attrs: item_impl.attrs.clone(),
465		})
466	}
467}