1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use super::CodegenError;
use heck::{ToSnakeCase as _, ToUpperCamelCase as _};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::typegen::ir::ToTokensWithSettings;
use scale_typegen::{typegen::ir::type_ir::CompositeIRKind, TypeGenerator};
use subxt_metadata::PalletMetadata;

/// Generate calls from the provided pallet's metadata. Each call returns a `StaticPayload`
/// that can be passed to the subxt client to submit/sign/encode.
///
/// # Arguments
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the calls are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::subxt::ext::subxt_core` when using subxt as a dependency.
pub fn generate_calls(
    type_gen: &TypeGenerator,
    pallet: &PalletMetadata,
    crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
    // Early return if the pallet has no calls.
    let Some(call_ty) = pallet.call_ty_id() else {
        return Ok(quote!());
    };

    let variant_names_and_struct_defs = super::generate_structs_from_variants(
        type_gen,
        call_ty,
        |name| name.to_upper_camel_case().into(),
        "Call",
    )?;
    let (call_structs, call_fns): (Vec<_>, Vec<_>) = variant_names_and_struct_defs
        .into_iter()
        .map(|var| {
            let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match &var.composite.kind {
                CompositeIRKind::Named(named_fields) => named_fields
                    .iter()
                    .map(|(name, field)| {
                        // Note: fn_arg_type this is relative the type path of the type alias when prefixed with `types::`, e.g. `set_max_code_size::New`
                        let fn_arg_type = field.type_path.to_token_stream(type_gen.settings());
                        let call_arg = if field.is_boxed {
                            quote! { #name: #crate_path::alloc::boxed::Box::new(#name) }
                        } else {
                            quote! { #name }
                        };
                        (quote!( #name: types::#fn_arg_type ), call_arg)
                    })
                    .unzip(),
                CompositeIRKind::NoFields => Default::default(),
                CompositeIRKind::Unnamed(_) => {
                    return Err(CodegenError::InvalidCallVariant(call_ty))
                }
            };

            let pallet_name = pallet.name();
            let call_name = &var.variant_name;
            let struct_name = &var.composite.name;
            let Some(call_hash) = pallet.call_hash(call_name) else {
                return Err(CodegenError::MissingCallMetadata(
                    pallet_name.into(),
                    call_name.to_string(),
                ));
            };
            let fn_name = format_ident!("{}", var.variant_name.to_snake_case());
            // Propagate the documentation just to `TransactionApi` methods, while
            // draining the documentation of inner call structures.
            let docs = &var.composite.docs;

            // this converts the composite into a full struct type. No Type Parameters needed here.
            let struct_def = type_gen
                .upcast_composite(&var.composite)
                .to_token_stream(type_gen.settings());
            let alias_mod = var.type_alias_mod;
            // The call structure's documentation was stripped above.
            let call_struct = quote! {
                #struct_def
                #alias_mod

                impl #crate_path::blocks::StaticExtrinsic for #struct_name {
                    const PALLET: &'static str = #pallet_name;
                    const CALL: &'static str = #call_name;
                }
            };

            let client_fn = quote! {
                #docs
                pub fn #fn_name(
                    &self,
                    #( #call_fn_args, )*
                ) -> #crate_path::tx::payload::StaticPayload<types::#struct_name> {
                    #crate_path::tx::payload::StaticPayload::new_static(
                        #pallet_name,
                        #call_name,
                        types::#struct_name { #( #call_args, )* },
                        [#(#call_hash,)*]
                    )
                }
            };

            Ok((call_struct, client_fn))
        })
        .collect::<Result<Vec<_>, _>>()?
        .into_iter()
        .unzip();

    let call_type = type_gen
        .resolve_type_path(call_ty)?
        .to_token_stream(type_gen.settings());
    let call_ty = type_gen.resolve_type(call_ty)?;
    let docs = type_gen.docs_from_scale_info(&call_ty.docs);

    let types_mod_ident = type_gen.types_mod_ident();

    Ok(quote! {
        #docs
        pub type Call = #call_type;
        pub mod calls {
            use super::root_mod;
            use super::#types_mod_ident;

            type DispatchError = #types_mod_ident::sp_runtime::DispatchError;

            pub mod types {
                use super::#types_mod_ident;

                #( #call_structs )*
            }

            pub struct TransactionApi;

            impl TransactionApi {
                #( #call_fns )*
            }
        }
    })
}