__client_accounts.rs 6.8 KB

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