1use core::str::FromStr;
19use frame_support_procedural_tools::syn_ext as ext;
20use proc_macro2::{Span, TokenStream};
21use quote::ToTokens;
22use std::collections::{HashMap, HashSet};
23use syn::{
24 ext::IdentExt,
25 parse::{Parse, ParseStream},
26 punctuated::Punctuated,
27 spanned::Spanned,
28 token, Attribute, Error, Ident, Path, Result, Token,
29};
30
31mod keyword {
32 syn::custom_keyword!(Block);
33 syn::custom_keyword!(NodeBlock);
34 syn::custom_keyword!(UncheckedExtrinsic);
35 syn::custom_keyword!(Pallet);
36 syn::custom_keyword!(Call);
37 syn::custom_keyword!(Storage);
38 syn::custom_keyword!(Event);
39 syn::custom_keyword!(Error);
40 syn::custom_keyword!(Config);
41 syn::custom_keyword!(Origin);
42 syn::custom_keyword!(Inherent);
43 syn::custom_keyword!(ValidateUnsigned);
44 syn::custom_keyword!(FreezeReason);
45 syn::custom_keyword!(HoldReason);
46 syn::custom_keyword!(Task);
47 syn::custom_keyword!(LockId);
48 syn::custom_keyword!(SlashReason);
49 syn::custom_keyword!(exclude_parts);
50 syn::custom_keyword!(use_parts);
51 syn::custom_keyword!(expanded);
52}
53
54#[derive(Debug)]
60pub enum RuntimeDeclaration {
61 Implicit(ImplicitRuntimeDeclaration),
62 Explicit(ExplicitRuntimeDeclaration),
63 ExplicitExpanded(ExplicitRuntimeDeclaration),
64}
65
66#[derive(Debug)]
68pub struct ImplicitRuntimeDeclaration {
69 pub pallets: Vec<PalletDeclaration>,
70}
71
72#[derive(Debug)]
74pub struct ExplicitRuntimeDeclaration {
75 pub name: Ident,
76 pub where_section: Option<WhereSection>,
77 pub pallets: Vec<Pallet>,
78 pub pallets_token: token::Brace,
79}
80
81impl Parse for RuntimeDeclaration {
82 fn parse(input: ParseStream) -> Result<Self> {
83 input.parse::<Token![pub]>()?;
84
85 if input.peek(Token![struct]) {
87 input.parse::<Token![struct]>()?;
88 } else {
89 input.parse::<Token![enum]>()?;
90 }
91
92 let name = input.parse::<syn::Ident>()?;
93 let where_section = if input.peek(token::Where) { Some(input.parse()?) } else { None };
94 let pallets =
95 input.parse::<ext::Braces<ext::Punctuated<PalletDeclaration, Token![,]>>>()?;
96 let pallets_token = pallets.token;
97
98 match convert_pallets(pallets.content.inner.into_iter().collect())? {
99 PalletsConversion::Implicit(pallets) =>
100 Ok(RuntimeDeclaration::Implicit(ImplicitRuntimeDeclaration { pallets })),
101 PalletsConversion::Explicit(pallets) =>
102 Ok(RuntimeDeclaration::Explicit(ExplicitRuntimeDeclaration {
103 name,
104 where_section,
105 pallets,
106 pallets_token,
107 })),
108 PalletsConversion::ExplicitExpanded(pallets) =>
109 Ok(RuntimeDeclaration::ExplicitExpanded(ExplicitRuntimeDeclaration {
110 name,
111 where_section,
112 pallets,
113 pallets_token,
114 })),
115 }
116 }
117}
118
119#[derive(Debug)]
120pub struct WhereSection {
121 pub span: Span,
122}
123
124impl Parse for WhereSection {
125 fn parse(input: ParseStream) -> Result<Self> {
126 input.parse::<token::Where>()?;
127
128 let mut definitions = Vec::new();
129 while !input.peek(token::Brace) {
130 let definition: WhereDefinition = input.parse()?;
131 definitions.push(definition);
132 if !input.peek(Token![,]) {
133 if !input.peek(token::Brace) {
134 return Err(input.error("Expected `,` or `{`"));
135 }
136 break;
137 }
138 input.parse::<Token![,]>()?;
139 }
140 remove_kind(input, WhereKind::Block, &mut definitions)?;
141 remove_kind(input, WhereKind::NodeBlock, &mut definitions)?;
142 remove_kind(input, WhereKind::UncheckedExtrinsic, &mut definitions)?;
143 if let Some(WhereDefinition { ref kind_span, ref kind, .. }) = definitions.first() {
144 let msg = format!(
145 "`{:?}` was declared above. Please use exactly one declaration for `{:?}`.",
146 kind, kind
147 );
148 return Err(Error::new(*kind_span, msg));
149 }
150 Ok(Self { span: input.span() })
151 }
152}
153
154#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
155pub enum WhereKind {
156 Block,
157 NodeBlock,
158 UncheckedExtrinsic,
159}
160
161#[derive(Debug)]
162pub struct WhereDefinition {
163 pub kind_span: Span,
164 pub kind: WhereKind,
165}
166
167impl Parse for WhereDefinition {
168 fn parse(input: ParseStream) -> Result<Self> {
169 let lookahead = input.lookahead1();
170 let (kind_span, kind) = if lookahead.peek(keyword::Block) {
171 (input.parse::<keyword::Block>()?.span(), WhereKind::Block)
172 } else if lookahead.peek(keyword::NodeBlock) {
173 (input.parse::<keyword::NodeBlock>()?.span(), WhereKind::NodeBlock)
174 } else if lookahead.peek(keyword::UncheckedExtrinsic) {
175 (input.parse::<keyword::UncheckedExtrinsic>()?.span(), WhereKind::UncheckedExtrinsic)
176 } else {
177 return Err(lookahead.error());
178 };
179
180 let _: Token![=] = input.parse()?;
181 let _: syn::TypePath = input.parse()?;
182
183 Ok(Self { kind_span, kind })
184 }
185}
186
187#[derive(Debug, Clone)]
189pub struct PalletDeclaration {
190 pub is_expanded: bool,
192 pub name: Ident,
194 pub attrs: Vec<Attribute>,
196 pub index: Option<u8>,
198 pub path: PalletPath,
200 pub instance: Option<Ident>,
202 pub pallet_parts: Option<Vec<PalletPart>>,
206 pub specified_parts: SpecifiedParts,
208}
209
210#[derive(Debug, Clone)]
212pub enum SpecifiedParts {
213 Exclude(Vec<PalletPartNoGeneric>),
215 Use(Vec<PalletPartNoGeneric>),
217 All,
219}
220
221impl Parse for PalletDeclaration {
222 fn parse(input: ParseStream) -> Result<Self> {
223 let attrs = input.call(Attribute::parse_outer)?;
224
225 let name = input.parse()?;
226 let _: Token![:] = input.parse()?;
227 let path = input.parse()?;
228
229 let instance = if input.peek(Token![::]) && input.peek3(Token![<]) {
231 let _: Token![::] = input.parse()?;
232 let _: Token![<] = input.parse()?;
233 let res = Some(input.parse()?);
234 let _: Token![>] = input.parse()?;
235 res
236 } else if !(input.peek(Token![::]) && input.peek3(token::Brace)) &&
237 !input.peek(keyword::expanded) &&
238 !input.peek(keyword::exclude_parts) &&
239 !input.peek(keyword::use_parts) &&
240 !input.peek(Token![=]) &&
241 !input.peek(Token![,]) &&
242 !input.is_empty()
243 {
244 return Err(input.error(
245 "Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,`",
246 ));
247 } else {
248 None
249 };
250
251 let (is_expanded, extra_parts) = if input.peek(keyword::expanded) {
253 let _: keyword::expanded = input.parse()?;
254 let _: Token![::] = input.parse()?;
255 (true, parse_pallet_parts(input)?)
256 } else {
257 (false, vec![])
258 };
259
260 let pallet_parts = if input.peek(Token![::]) && input.peek3(token::Brace) {
262 let _: Token![::] = input.parse()?;
263 let mut parts = parse_pallet_parts(input)?;
264 parts.extend(extra_parts.into_iter());
265 Some(parts)
266 } else if !input.peek(keyword::exclude_parts) &&
267 !input.peek(keyword::use_parts) &&
268 !input.peek(Token![=]) &&
269 !input.peek(Token![,]) &&
270 !input.is_empty()
271 {
272 return Err(input.error(
273 "Unexpected tokens, expected one of `::{`, `exclude_parts`, `use_parts`, `=`, `,`",
274 ));
275 } else {
276 is_expanded.then_some(extra_parts)
277 };
278
279 let specified_parts = if input.peek(keyword::exclude_parts) {
281 let _: keyword::exclude_parts = input.parse()?;
282 SpecifiedParts::Exclude(parse_pallet_parts_no_generic(input)?)
283 } else if input.peek(keyword::use_parts) {
284 let _: keyword::use_parts = input.parse()?;
285 SpecifiedParts::Use(parse_pallet_parts_no_generic(input)?)
286 } else if !input.peek(Token![=]) && !input.peek(Token![,]) && !input.is_empty() {
287 return Err(input.error("Unexpected tokens, expected one of `exclude_parts`, `=`, `,`"));
288 } else {
289 SpecifiedParts::All
290 };
291
292 let index = if input.peek(Token![=]) {
294 input.parse::<Token![=]>()?;
295 let index = input.parse::<syn::LitInt>()?;
296 let index = index.base10_parse::<u8>()?;
297 Some(index)
298 } else if !input.peek(Token![,]) && !input.is_empty() {
299 return Err(input.error("Unexpected tokens, expected one of `=`, `,`"));
300 } else {
301 None
302 };
303
304 Ok(Self { is_expanded, attrs, name, path, instance, pallet_parts, specified_parts, index })
305 }
306}
307
308#[derive(Debug, Clone)]
313pub struct PalletPath {
314 pub inner: Path,
315}
316
317impl PalletPath {
318 pub fn module_name(&self) -> String {
319 self.inner.segments.iter().fold(String::new(), |mut acc, segment| {
320 if !acc.is_empty() {
321 acc.push_str("::");
322 }
323 acc.push_str(&segment.ident.to_string());
324 acc
325 })
326 }
327}
328
329impl Parse for PalletPath {
330 fn parse(input: ParseStream) -> Result<Self> {
331 let mut res =
332 PalletPath { inner: Path { leading_colon: None, segments: Punctuated::new() } };
333
334 let lookahead = input.lookahead1();
335 if lookahead.peek(Token![crate]) ||
336 lookahead.peek(Token![self]) ||
337 lookahead.peek(Token![super]) ||
338 lookahead.peek(Ident)
339 {
340 let ident = input.call(Ident::parse_any)?;
341 res.inner.segments.push(ident.into());
342 } else {
343 return Err(lookahead.error());
344 }
345
346 while input.peek(Token![::]) && input.peek3(Ident) {
347 input.parse::<Token![::]>()?;
348 let ident = input.parse::<Ident>()?;
349 res.inner.segments.push(ident.into());
350 }
351 Ok(res)
352 }
353}
354
355impl quote::ToTokens for PalletPath {
356 fn to_tokens(&self, tokens: &mut TokenStream) {
357 self.inner.to_tokens(tokens);
358 }
359}
360
361fn parse_pallet_parts(input: ParseStream) -> Result<Vec<PalletPart>> {
365 let pallet_parts: ext::Braces<ext::Punctuated<PalletPart, Token![,]>> = input.parse()?;
366
367 let mut resolved = HashSet::new();
368 for part in pallet_parts.content.inner.iter() {
369 if !resolved.insert(part.name()) {
370 let msg = format!(
371 "`{}` was already declared before. Please remove the duplicate declaration",
372 part.name(),
373 );
374 return Err(Error::new(part.keyword.span(), msg));
375 }
376 }
377
378 Ok(pallet_parts.content.inner.into_iter().collect())
379}
380
381#[derive(Debug, Clone)]
382pub enum PalletPartKeyword {
383 Pallet(keyword::Pallet),
384 Call(keyword::Call),
385 Storage(keyword::Storage),
386 Event(keyword::Event),
387 Error(keyword::Error),
388 Config(keyword::Config),
389 Origin(keyword::Origin),
390 Inherent(keyword::Inherent),
391 ValidateUnsigned(keyword::ValidateUnsigned),
392 FreezeReason(keyword::FreezeReason),
393 HoldReason(keyword::HoldReason),
394 Task(keyword::Task),
395 LockId(keyword::LockId),
396 SlashReason(keyword::SlashReason),
397}
398
399impl Parse for PalletPartKeyword {
400 fn parse(input: ParseStream) -> Result<Self> {
401 let lookahead = input.lookahead1();
402
403 if lookahead.peek(keyword::Pallet) {
404 Ok(Self::Pallet(input.parse()?))
405 } else if lookahead.peek(keyword::Call) {
406 Ok(Self::Call(input.parse()?))
407 } else if lookahead.peek(keyword::Storage) {
408 Ok(Self::Storage(input.parse()?))
409 } else if lookahead.peek(keyword::Event) {
410 Ok(Self::Event(input.parse()?))
411 } else if lookahead.peek(keyword::Error) {
412 Ok(Self::Error(input.parse()?))
413 } else if lookahead.peek(keyword::Config) {
414 Ok(Self::Config(input.parse()?))
415 } else if lookahead.peek(keyword::Origin) {
416 Ok(Self::Origin(input.parse()?))
417 } else if lookahead.peek(keyword::Inherent) {
418 Ok(Self::Inherent(input.parse()?))
419 } else if lookahead.peek(keyword::ValidateUnsigned) {
420 Ok(Self::ValidateUnsigned(input.parse()?))
421 } else if lookahead.peek(keyword::FreezeReason) {
422 Ok(Self::FreezeReason(input.parse()?))
423 } else if lookahead.peek(keyword::HoldReason) {
424 Ok(Self::HoldReason(input.parse()?))
425 } else if lookahead.peek(keyword::Task) {
426 Ok(Self::Task(input.parse()?))
427 } else if lookahead.peek(keyword::LockId) {
428 Ok(Self::LockId(input.parse()?))
429 } else if lookahead.peek(keyword::SlashReason) {
430 Ok(Self::SlashReason(input.parse()?))
431 } else {
432 Err(lookahead.error())
433 }
434 }
435}
436
437impl PalletPartKeyword {
438 fn name(&self) -> &'static str {
440 match self {
441 Self::Pallet(_) => "Pallet",
442 Self::Call(_) => "Call",
443 Self::Storage(_) => "Storage",
444 Self::Event(_) => "Event",
445 Self::Error(_) => "Error",
446 Self::Config(_) => "Config",
447 Self::Origin(_) => "Origin",
448 Self::Inherent(_) => "Inherent",
449 Self::ValidateUnsigned(_) => "ValidateUnsigned",
450 Self::FreezeReason(_) => "FreezeReason",
451 Self::HoldReason(_) => "HoldReason",
452 Self::Task(_) => "Task",
453 Self::LockId(_) => "LockId",
454 Self::SlashReason(_) => "SlashReason",
455 }
456 }
457
458 fn allows_generic(&self) -> bool {
460 Self::all_generic_arg().iter().any(|n| *n == self.name())
461 }
462
463 fn all_generic_arg() -> &'static [&'static str] {
465 &["Event", "Error", "Origin", "Config", "Task"]
466 }
467}
468
469impl ToTokens for PalletPartKeyword {
470 fn to_tokens(&self, tokens: &mut TokenStream) {
471 match self {
472 Self::Pallet(inner) => inner.to_tokens(tokens),
473 Self::Call(inner) => inner.to_tokens(tokens),
474 Self::Storage(inner) => inner.to_tokens(tokens),
475 Self::Event(inner) => inner.to_tokens(tokens),
476 Self::Error(inner) => inner.to_tokens(tokens),
477 Self::Config(inner) => inner.to_tokens(tokens),
478 Self::Origin(inner) => inner.to_tokens(tokens),
479 Self::Inherent(inner) => inner.to_tokens(tokens),
480 Self::ValidateUnsigned(inner) => inner.to_tokens(tokens),
481 Self::FreezeReason(inner) => inner.to_tokens(tokens),
482 Self::HoldReason(inner) => inner.to_tokens(tokens),
483 Self::Task(inner) => inner.to_tokens(tokens),
484 Self::LockId(inner) => inner.to_tokens(tokens),
485 Self::SlashReason(inner) => inner.to_tokens(tokens),
486 }
487 }
488}
489
490#[derive(Debug, Clone)]
491pub struct PalletPart {
492 pub keyword: PalletPartKeyword,
493 pub generics: syn::Generics,
494}
495
496impl Parse for PalletPart {
497 fn parse(input: ParseStream) -> Result<Self> {
498 let keyword: PalletPartKeyword = input.parse()?;
499
500 let generics: syn::Generics = input.parse()?;
501 if !generics.params.is_empty() && !keyword.allows_generic() {
502 let valid_generics = PalletPart::format_names(PalletPartKeyword::all_generic_arg());
503 let msg = format!(
504 "`{}` is not allowed to have generics. \
505 Only the following pallets are allowed to have generics: {}.",
506 keyword.name(),
507 valid_generics,
508 );
509 return Err(syn::Error::new(keyword.span(), msg));
510 }
511
512 Ok(Self { keyword, generics })
513 }
514}
515
516impl PalletPart {
517 pub fn format_names(names: &[&'static str]) -> String {
518 let res: Vec<_> = names.iter().map(|s| format!("`{}`", s)).collect();
519 res.join(", ")
520 }
521
522 pub fn name(&self) -> &'static str {
524 self.keyword.name()
525 }
526}
527
528fn remove_kind(
529 input: ParseStream,
530 kind: WhereKind,
531 definitions: &mut Vec<WhereDefinition>,
532) -> Result<WhereDefinition> {
533 if let Some(pos) = definitions.iter().position(|d| d.kind == kind) {
534 Ok(definitions.remove(pos))
535 } else {
536 let msg = format!(
537 "Missing associated type for `{:?}`. Add `{:?}` = ... to where section.",
538 kind, kind
539 );
540 Err(input.error(msg))
541 }
542}
543
544#[derive(Debug, Clone)]
546pub struct PalletPartNoGeneric {
547 keyword: PalletPartKeyword,
548}
549
550impl Parse for PalletPartNoGeneric {
551 fn parse(input: ParseStream) -> Result<Self> {
552 Ok(Self { keyword: input.parse()? })
553 }
554}
555
556fn parse_pallet_parts_no_generic(input: ParseStream) -> Result<Vec<PalletPartNoGeneric>> {
560 let pallet_parts: ext::Braces<ext::Punctuated<PalletPartNoGeneric, Token![,]>> =
561 input.parse()?;
562
563 let mut resolved = HashSet::new();
564 for part in pallet_parts.content.inner.iter() {
565 if !resolved.insert(part.keyword.name()) {
566 let msg = format!(
567 "`{}` was already declared before. Please remove the duplicate declaration",
568 part.keyword.name(),
569 );
570 return Err(Error::new(part.keyword.span(), msg));
571 }
572 }
573
574 Ok(pallet_parts.content.inner.into_iter().collect())
575}
576
577#[derive(Debug, Clone)]
579pub struct Pallet {
580 pub is_expanded: bool,
582 pub name: Ident,
584 pub index: u8,
586 pub path: PalletPath,
588 pub instance: Option<Ident>,
590 pub pallet_parts: Vec<PalletPart>,
592 pub cfg_pattern: Vec<cfg_expr::Expression>,
594 pub docs: Vec<syn::Expr>,
596}
597
598impl Pallet {
599 pub fn pallet_parts(&self) -> &[PalletPart] {
601 &self.pallet_parts
602 }
603
604 pub fn find_part(&self, name: &str) -> Option<&PalletPart> {
606 self.pallet_parts.iter().find(|part| part.name() == name)
607 }
608
609 pub fn exists_part(&self, name: &str) -> bool {
611 self.find_part(name).is_some()
612 }
613
614 pub fn get_attributes(&self) -> TokenStream {
616 self.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| {
617 let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original()))
618 .expect("was successfully parsed before; qed");
619 quote::quote! {
620 #acc
621 #attr
622 }
623 })
624 }
625}
626
627enum PalletsConversion {
637 Implicit(Vec<PalletDeclaration>),
641 Explicit(Vec<Pallet>),
648 ExplicitExpanded(Vec<Pallet>),
657}
658
659fn convert_pallets(pallets: Vec<PalletDeclaration>) -> syn::Result<PalletsConversion> {
665 if pallets.iter().any(|pallet| pallet.pallet_parts.is_none()) {
666 return Ok(PalletsConversion::Implicit(pallets));
667 }
668
669 let mut indices = HashMap::new();
670 let mut last_index: Option<u8> = None;
671 let mut names = HashMap::new();
672 let mut is_expanded = true;
673
674 let pallets = pallets
675 .into_iter()
676 .map(|pallet| {
677 let final_index = match pallet.index {
678 Some(i) => i,
679 None => last_index.map_or(Some(0), |i| i.checked_add(1)).ok_or_else(|| {
680 let msg = "Pallet index doesn't fit into u8, index is 256";
681 syn::Error::new(pallet.name.span(), msg)
682 })?,
683 };
684
685 last_index = Some(final_index);
686
687 if let Some(used_pallet) = indices.insert(final_index, pallet.name.clone()) {
688 let msg = format!(
689 "Pallet indices are conflicting: Both pallets {} and {} are at index {}",
690 used_pallet, pallet.name, final_index,
691 );
692 let mut err = syn::Error::new(used_pallet.span(), &msg);
693 err.combine(syn::Error::new(pallet.name.span(), msg));
694 return Err(err);
695 }
696
697 if let Some(used_pallet) = names.insert(pallet.name.clone(), pallet.name.span()) {
698 let msg = "Two pallets with the same name!";
699
700 let mut err = syn::Error::new(used_pallet, &msg);
701 err.combine(syn::Error::new(pallet.name.span(), &msg));
702 return Err(err);
703 }
704
705 let mut pallet_parts = pallet.pallet_parts.expect("Checked above");
706
707 let available_parts =
708 pallet_parts.iter().map(|part| part.keyword.name()).collect::<HashSet<_>>();
709
710 match &pallet.specified_parts {
712 SpecifiedParts::Exclude(parts) | SpecifiedParts::Use(parts) =>
713 for part in parts {
714 if !available_parts.contains(part.keyword.name()) {
715 let msg = format!(
716 "Invalid pallet part specified, the pallet `{}` doesn't have the \
717 `{}` part. Available parts are: {}.",
718 pallet.name,
719 part.keyword.name(),
720 pallet_parts.iter().fold(String::new(), |fold, part| {
721 if fold.is_empty() {
722 format!("`{}`", part.keyword.name())
723 } else {
724 format!("{}, `{}`", fold, part.keyword.name())
725 }
726 })
727 );
728 return Err(syn::Error::new(part.keyword.span(), msg));
729 }
730 },
731 SpecifiedParts::All => (),
732 }
733
734 match pallet.specified_parts {
736 SpecifiedParts::Exclude(excluded_parts) => pallet_parts.retain(|part| {
737 !excluded_parts
738 .iter()
739 .any(|excluded_part| excluded_part.keyword.name() == part.keyword.name())
740 }),
741 SpecifiedParts::Use(used_parts) => pallet_parts.retain(|part| {
742 used_parts.iter().any(|use_part| use_part.keyword.name() == part.keyword.name())
743 }),
744 SpecifiedParts::All => (),
745 }
746
747 let cfg_pattern = pallet
748 .attrs
749 .iter()
750 .map(|attr| {
751 if attr.path().segments.first().map_or(false, |s| s.ident != "cfg") {
752 let msg = "Unsupported attribute, only #[cfg] is supported on pallet \
753 declarations in `construct_runtime`";
754 return Err(syn::Error::new(attr.span(), msg));
755 }
756
757 attr.parse_args_with(|input: syn::parse::ParseStream| {
758 let input = input.parse::<proc_macro2::TokenStream>()?;
761 cfg_expr::Expression::parse(&input.to_string())
762 .map_err(|e| syn::Error::new(attr.span(), e.to_string()))
763 })
764 })
765 .collect::<Result<Vec<_>>>()?;
766
767 is_expanded &= pallet.is_expanded;
768
769 Ok(Pallet {
770 is_expanded: pallet.is_expanded,
771 name: pallet.name,
772 index: final_index,
773 path: pallet.path,
774 instance: pallet.instance,
775 cfg_pattern,
776 pallet_parts,
777 docs: vec![],
778 })
779 })
780 .collect::<Result<Vec<_>>>()?;
781
782 if is_expanded {
783 Ok(PalletsConversion::ExplicitExpanded(pallets))
784 } else {
785 Ok(PalletsConversion::Explicit(pallets))
786 }
787}