1use 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
25mod 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
40pub struct CallDef {
42 pub where_clause: Option<syn::WhereClause>,
44 pub instances: Vec<helper::InstanceUsage>,
46 pub index: usize,
48 pub methods: Vec<CallVariantDef>,
50 pub attr_span: proc_macro2::Span,
52 pub docs: Vec<syn::Expr>,
54}
55
56#[derive(Clone)]
58pub enum CallWeightDef {
59 Immediate(syn::Expr),
62
63 DevModeDefault,
65
66 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#[derive(Clone)]
89pub struct CallVariantDef {
90 pub name: syn::Ident,
92 pub args: Vec<(bool, syn::Ident, Box<syn::Type>)>,
94 pub weight: CallWeightDef,
96 pub call_index: u8,
98 pub explicit_call_index: bool,
100 pub docs: Vec<syn::Expr>,
102 pub attrs: Vec<syn::Attribute>,
104 pub cfg_attrs: Vec<syn::Attribute>,
106 pub feeless_check: Option<syn::ExprClosure>,
108 pub return_type: helper::CallReturnType,
110 pub authorize: Option<AuthorizeDef>,
113}
114
115#[derive(Clone)]
117pub struct AuthorizeDef {
118 pub expr: syn::Expr,
120 pub weight: CallWeightDef,
123}
124
125pub enum FunctionAttr {
127 CallIndex(u8),
129 Weight(syn::Expr),
131 FeelessIf(Span, syn::ExprClosure),
133 Authorize(syn::Expr),
135 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
192pub 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
209pub 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}