anchor_syn/codegen/accounts/
__cpi_client_accounts.rs

1use std::str::FromStr;
2
3use crate::{AccountField, AccountsStruct, Ty};
4use heck::SnakeCase;
5use quote::quote;
6
7// Generates the private `__cpi_client_accounts` mod implementation, containing
8// a generated struct mapping 1-1 to the `Accounts` struct, except with
9// `AccountInfo`s as the types. This is generated for CPI clients.
10pub fn generate(
11    accs: &AccountsStruct,
12    program_id: proc_macro2::TokenStream,
13) -> proc_macro2::TokenStream {
14    let name = &accs.ident;
15    let account_mod_name: proc_macro2::TokenStream = format!(
16        "__cpi_client_accounts_{}",
17        accs.ident.to_string().to_snake_case()
18    )
19    .parse()
20    .unwrap();
21
22    let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
23        .fields
24        .iter()
25        .map(|f: &AccountField| match f {
26            AccountField::CompositeField(s) => {
27                let name = &s.ident;
28                let docs = if let Some(ref docs) = s.docs {
29                    docs.iter()
30                        .map(|docs_line| {
31                            proc_macro2::TokenStream::from_str(&format!(
32                                "#[doc = r#\"{docs_line}\"#]"
33                            ))
34                            .unwrap()
35                        })
36                        .collect()
37                } else {
38                    quote!()
39                };
40                let symbol: proc_macro2::TokenStream = format!(
41                    "__cpi_client_accounts_{0}::{1}",
42                    s.symbol.to_snake_case(),
43                    s.symbol,
44                )
45                .parse()
46                .unwrap();
47                quote! {
48                    #docs
49                    pub #name: #symbol<'info>
50                }
51            }
52            AccountField::Field(f) => {
53                let name = &f.ident;
54                let docs = if let Some(ref docs) = f.docs {
55                    docs.iter()
56                        .map(|docs_line| {
57                            proc_macro2::TokenStream::from_str(&format!(
58                                "#[doc = r#\"{docs_line}\"#]"
59                            ))
60                            .unwrap()
61                        })
62                        .collect()
63                } else {
64                    quote!()
65                };
66                if f.is_optional {
67                    quote! {
68                        #docs
69                        pub #name: Option<anchor_lang::solana_program::account_info::AccountInfo<'info>>
70                    }
71                } else {
72                    quote! {
73                        #docs
74                        pub #name: anchor_lang::solana_program::account_info::AccountInfo<'info>
75                    }
76                }
77            }
78        })
79        .collect();
80
81    let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
82        .fields
83        .iter()
84        .map(|f: &AccountField| match f {
85            AccountField::CompositeField(s) => {
86                let name = &s.ident;
87                quote! {
88                    account_metas.extend(self.#name.to_account_metas(None));
89                }
90            }
91            AccountField::Field(f) => {
92                let is_signer = match f.ty {
93                    Ty::Signer => true,
94                    _ => f.constraints.is_signer(),
95                };
96                let is_signer = match is_signer {
97                    false => quote! {false},
98                    true => quote! {true},
99                };
100                let meta = match f.constraints.is_mutable() {
101                    false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
102                    true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
103                };
104                let name = &f.ident;
105                if f.is_optional {
106                    quote! {
107                        if let Some(#name) = &self.#name {
108                            account_metas.push(#meta(anchor_lang::Key::key(#name), #is_signer));
109                        } else {
110                            account_metas.push(anchor_lang::solana_program::instruction::AccountMeta::new_readonly(#program_id, false));
111                        }
112                    }
113                } else {
114                    quote! {
115                        account_metas.push(#meta(anchor_lang::Key::key(&self.#name), #is_signer));
116                    }
117                }
118            }
119        })
120        .collect();
121
122    let account_struct_infos: Vec<proc_macro2::TokenStream> = accs
123        .fields
124        .iter()
125        .map(|f: &AccountField| {
126            let name = &f.ident();
127            quote! {
128                account_infos.extend(anchor_lang::ToAccountInfos::to_account_infos(&self.#name));
129            }
130        })
131        .collect();
132
133    // Re-export all composite account structs (i.e. other structs deriving
134    // accounts embedded into this struct. Required because, these embedded
135    // structs are *not* visible from the #[program] macro, which is responsible
136    // for generating the `accounts` mod, which aggregates all the generated
137    // accounts used for structs.
138    let re_exports: Vec<proc_macro2::TokenStream> = {
139        // First, dedup the exports.
140        let mut re_exports = std::collections::HashSet::new();
141        for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
142            AccountField::CompositeField(s) => Some(s),
143            AccountField::Field(_) => None,
144        }) {
145            re_exports.insert(format!(
146                "__cpi_client_accounts_{0}::{1}",
147                f.symbol.to_snake_case(),
148                f.symbol,
149            ));
150        }
151
152        re_exports
153            .iter()
154            .map(|symbol: &String| {
155                let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
156                quote! {
157                    pub use #symbol;
158                }
159            })
160            .collect()
161    };
162    let generics = if account_struct_fields.is_empty() {
163        quote! {}
164    } else {
165        quote! {<'info>}
166    };
167    let struct_doc = proc_macro2::TokenStream::from_str(&format!(
168        "#[doc = \" Generated CPI struct of the accounts for [`{name}`].\"]"
169    ))
170    .unwrap();
171    quote! {
172        /// An internal, Anchor generated module. This is used (as an
173        /// implementation detail), to generate a CPI struct for a given
174        /// `#[derive(Accounts)]` implementation, where each field is an
175        /// AccountInfo.
176        ///
177        /// To access the struct in this module, one should use the sibling
178        /// [`cpi::accounts`] module (also generated), which re-exports this.
179        pub(crate) mod #account_mod_name {
180            use super::*;
181
182            #(#re_exports)*
183
184            #struct_doc
185            pub struct #name #generics {
186                #(#account_struct_fields),*
187            }
188
189            #[automatically_derived]
190            impl #generics anchor_lang::ToAccountMetas for #name #generics {
191                fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
192                    let mut account_metas = vec![];
193                    #(#account_struct_metas)*
194                    account_metas
195                }
196            }
197
198            #[automatically_derived]
199            impl<'info> anchor_lang::ToAccountInfos<'info> for #name #generics {
200                fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
201                    let mut account_infos = vec![];
202                    #(#account_struct_infos)*
203                    account_infos
204                }
205            }
206        }
207    }
208}