accounts.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. use crate::{
  2. AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo,
  3. ConstraintExecutable, ConstraintLiteral, ConstraintOwner, ConstraintRentExempt,
  4. ConstraintSeeds, ConstraintSigner, Field, Ty,
  5. };
  6. use heck::SnakeCase;
  7. use quote::quote;
  8. pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
  9. // Deserialization for each field.
  10. let deser_fields: Vec<proc_macro2::TokenStream> = accs
  11. .fields
  12. .iter()
  13. .map(|af: &AccountField| match af {
  14. AccountField::AccountsStruct(s) => {
  15. let name = &s.ident;
  16. let ty = &s.raw_field.ty;
  17. quote! {
  18. let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
  19. }
  20. }
  21. AccountField::Field(f) => {
  22. let name = f.typed_ident();
  23. match f.is_init {
  24. false => quote! {
  25. let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
  26. },
  27. true => quote! {
  28. let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
  29. },
  30. }
  31. }
  32. })
  33. .collect();
  34. // Constraint checks for each account fields.
  35. let access_checks: Vec<proc_macro2::TokenStream> = accs
  36. .fields
  37. .iter()
  38. .map(|af: &AccountField| {
  39. let checks: Vec<proc_macro2::TokenStream> = match af {
  40. AccountField::Field(f) => f
  41. .constraints
  42. .iter()
  43. .map(|c| generate_field_constraint(&f, c))
  44. .collect(),
  45. AccountField::AccountsStruct(s) => s
  46. .constraints
  47. .iter()
  48. .map(|c| generate_composite_constraint(&s, c))
  49. .collect(),
  50. };
  51. quote! {
  52. #(#checks)*
  53. }
  54. })
  55. .collect();
  56. // Each field in the final deserialized accounts struct.
  57. let return_tys: Vec<proc_macro2::TokenStream> = accs
  58. .fields
  59. .iter()
  60. .map(|f: &AccountField| {
  61. let name = match f {
  62. AccountField::AccountsStruct(s) => &s.ident,
  63. AccountField::Field(f) => &f.ident,
  64. };
  65. quote! {
  66. #name
  67. }
  68. })
  69. .collect();
  70. // Exit program code-blocks for each account.
  71. let on_save: Vec<proc_macro2::TokenStream> = accs
  72. .fields
  73. .iter()
  74. .map(|af: &AccountField| match af {
  75. AccountField::AccountsStruct(s) => {
  76. let name = &s.ident;
  77. quote! {
  78. anchor_lang::AccountsExit::exit(&self.#name, program_id)?;
  79. }
  80. }
  81. AccountField::Field(f) => {
  82. let ident = &f.ident;
  83. match f.is_mut {
  84. false => quote! {},
  85. true => quote! {
  86. anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
  87. },
  88. }
  89. }
  90. })
  91. .collect();
  92. // Implementation for `ToAccountInfos` trait.
  93. let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
  94. .fields
  95. .iter()
  96. .map(|f: &AccountField| {
  97. let name = match f {
  98. AccountField::AccountsStruct(s) => &s.ident,
  99. AccountField::Field(f) => &f.ident,
  100. };
  101. quote! {
  102. account_infos.extend(self.#name.to_account_infos());
  103. }
  104. })
  105. .collect();
  106. // Implementation for `ToAccountMetas` trait.
  107. let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
  108. .fields
  109. .iter()
  110. .map(|f: &AccountField| {
  111. let (name, is_signer) = match f {
  112. AccountField::AccountsStruct(s) => (&s.ident, quote! {None}),
  113. AccountField::Field(f) => {
  114. let is_signer = match f.is_signer {
  115. false => quote! {None},
  116. true => quote! {Some(true)},
  117. };
  118. (&f.ident, is_signer)
  119. }
  120. };
  121. quote! {
  122. account_metas.extend(self.#name.to_account_metas(#is_signer));
  123. }
  124. })
  125. .collect();
  126. let name = &accs.ident;
  127. let (combined_generics, trait_generics, strct_generics) = match accs.generics.lt_token {
  128. None => (quote! {<'info>}, quote! {<'info>}, quote! {}),
  129. Some(_) => {
  130. let g = &accs.generics;
  131. (quote! {#g}, quote! {#g}, quote! {#g})
  132. }
  133. };
  134. let account_mod_name: proc_macro2::TokenStream = format!(
  135. "__client_accounts_{}",
  136. accs.ident.to_string().to_snake_case()
  137. )
  138. .parse()
  139. .unwrap();
  140. let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
  141. .fields
  142. .iter()
  143. .map(|f: &AccountField| match f {
  144. AccountField::AccountsStruct(s) => {
  145. let name = &s.ident;
  146. let symbol: proc_macro2::TokenStream = format!(
  147. "__client_accounts_{0}::{1}",
  148. s.symbol.to_snake_case(),
  149. s.symbol,
  150. )
  151. .parse()
  152. .unwrap();
  153. quote! {
  154. pub #name: #symbol
  155. }
  156. }
  157. AccountField::Field(f) => {
  158. let name = &f.ident;
  159. quote! {
  160. pub #name: anchor_lang::solana_program::pubkey::Pubkey
  161. }
  162. }
  163. })
  164. .collect();
  165. let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
  166. .fields
  167. .iter()
  168. .map(|f: &AccountField| match f {
  169. AccountField::AccountsStruct(s) => {
  170. let name = &s.ident;
  171. quote! {
  172. account_metas.extend(self.#name.to_account_metas(None));
  173. }
  174. }
  175. AccountField::Field(f) => {
  176. let is_signer = match f.is_signer {
  177. false => quote! {false},
  178. true => quote! {true},
  179. };
  180. let meta = match f.is_mut {
  181. false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
  182. true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
  183. };
  184. let name = &f.ident;
  185. quote! {
  186. account_metas.push(#meta(self.#name, #is_signer));
  187. }
  188. }
  189. })
  190. .collect();
  191. // Re-export all composite account structs (i.e. other structs deriving
  192. // accounts embedded into this struct. Required because, these embedded
  193. // structs are *not* visible from the #[program] macro, which is responsible
  194. // for generating the `accounts` mod, which aggregates all the the generated
  195. // accounts used for structs.
  196. let re_exports: Vec<proc_macro2::TokenStream> = {
  197. // First, dedup the exports.
  198. let mut re_exports = std::collections::HashSet::new();
  199. for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
  200. AccountField::AccountsStruct(s) => Some(s),
  201. AccountField::Field(_) => None,
  202. }) {
  203. re_exports.insert(format!(
  204. "__client_accounts_{0}::{1}",
  205. f.symbol.to_snake_case(),
  206. f.symbol,
  207. ));
  208. }
  209. re_exports
  210. .iter()
  211. .map(|symbol: &String| {
  212. let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
  213. quote! {
  214. pub use #symbol;
  215. }
  216. })
  217. .collect()
  218. };
  219. quote! {
  220. /// An internal, Anchor generated module. This is used (as an
  221. /// implementation detail), to generate a struct for a given
  222. /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
  223. /// instead of an `AccountInfo`. This is useful for clients that want
  224. /// to generate a list of accounts, without explicitly knowing the
  225. /// order all the fields should be in.
  226. ///
  227. /// To access the struct in this module, one should use the sibling
  228. /// `accounts` module (also generated), which re-exports this.
  229. mod #account_mod_name {
  230. use super::*;
  231. use anchor_lang::prelude::borsh;
  232. #(#re_exports)*
  233. #[derive(anchor_lang::AnchorSerialize)]
  234. pub struct #name {
  235. #(#account_struct_fields),*
  236. }
  237. impl anchor_lang::ToAccountMetas for #name {
  238. fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
  239. let mut account_metas = vec![];
  240. #(#account_struct_metas)*
  241. account_metas
  242. }
  243. }
  244. }
  245. impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
  246. #[inline(never)]
  247. fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
  248. // Deserialize each account.
  249. #(#deser_fields)*
  250. // Perform constraint checks on each account.
  251. #(#access_checks)*
  252. // Success. Return the validated accounts.
  253. Ok(#name {
  254. #(#return_tys),*
  255. })
  256. }
  257. }
  258. impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
  259. fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
  260. let mut account_infos = vec![];
  261. #(#to_acc_infos)*
  262. account_infos
  263. }
  264. }
  265. impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
  266. fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
  267. let mut account_metas = vec![];
  268. #(#to_acc_metas)*
  269. account_metas
  270. }
  271. }
  272. impl#combined_generics anchor_lang::AccountsExit#trait_generics for #name#strct_generics {
  273. fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
  274. #(#on_save)*
  275. Ok(())
  276. }
  277. }
  278. }
  279. }
  280. pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
  281. match c {
  282. Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
  283. Constraint::Signer(c) => generate_constraint_signer(f, c),
  284. Constraint::Literal(c) => generate_constraint_literal(c),
  285. Constraint::Owner(c) => generate_constraint_owner(f, c),
  286. Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
  287. Constraint::Seeds(c) => generate_constraint_seeds(f, c),
  288. Constraint::Executable(c) => generate_constraint_executable(f, c),
  289. }
  290. }
  291. pub fn generate_composite_constraint(
  292. _f: &CompositeField,
  293. c: &Constraint,
  294. ) -> proc_macro2::TokenStream {
  295. match c {
  296. Constraint::Literal(c) => generate_constraint_literal(c),
  297. _ => panic!("Composite fields can only use literal constraints"),
  298. }
  299. }
  300. pub fn generate_constraint_belongs_to(
  301. f: &Field,
  302. c: &ConstraintBelongsTo,
  303. ) -> proc_macro2::TokenStream {
  304. let target = c.join_target.clone();
  305. let ident = &f.ident;
  306. quote! {
  307. if &#ident.#target != #target.to_account_info().key {
  308. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  309. }
  310. }
  311. }
  312. pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
  313. let ident = &f.ident;
  314. let info = match f.ty {
  315. Ty::AccountInfo => quote! { #ident },
  316. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  317. _ => panic!("Invalid syntax: signer cannot be specified."),
  318. };
  319. quote! {
  320. // Don't enforce on CPI, since usually a program is signing and so
  321. // the `try_accounts` deserializatoin will fail *if* the one
  322. // tries to manually invoke it.
  323. //
  324. // This check will be performed on the other end of the invocation.
  325. if cfg!(not(feature = "cpi")) {
  326. if !#info.is_signer {
  327. return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
  328. }
  329. }
  330. }
  331. }
  332. pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
  333. let tokens = &c.tokens;
  334. quote! {
  335. if !(#tokens) {
  336. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  337. }
  338. }
  339. }
  340. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  341. let ident = &f.ident;
  342. let info = match f.ty {
  343. Ty::AccountInfo => quote! { #ident },
  344. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  345. _ => panic!("Invalid syntax: owner cannot be specified."),
  346. };
  347. match c {
  348. ConstraintOwner::Skip => quote! {},
  349. ConstraintOwner::Program => quote! {
  350. if #info.owner != program_id {
  351. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  352. }
  353. },
  354. }
  355. }
  356. pub fn generate_constraint_rent_exempt(
  357. f: &Field,
  358. c: &ConstraintRentExempt,
  359. ) -> proc_macro2::TokenStream {
  360. let ident = &f.ident;
  361. let info = match f.ty {
  362. Ty::AccountInfo => quote! { #ident },
  363. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  364. _ => panic!("Invalid syntax: rent exemption cannot be specified."),
  365. };
  366. match c {
  367. ConstraintRentExempt::Skip => quote! {},
  368. ConstraintRentExempt::Enforce => quote! {
  369. if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  370. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
  371. }
  372. },
  373. }
  374. }
  375. pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2::TokenStream {
  376. let name = &f.ident;
  377. let seeds = &c.seeds;
  378. quote! {
  379. let program_signer = Pubkey::create_program_address(
  380. &#seeds,
  381. program_id,
  382. ).map_err(|_| anchor_lang::solana_program::program_error::ProgramError::Custom(1))?; // todo
  383. if #name.to_account_info().key != &program_signer {
  384. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
  385. }
  386. }
  387. }
  388. pub fn generate_constraint_executable(
  389. f: &Field,
  390. _c: &ConstraintExecutable,
  391. ) -> proc_macro2::TokenStream {
  392. let name = &f.ident;
  393. quote! {
  394. if !#name.to_account_info().executable {
  395. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(5)) // todo
  396. }
  397. }
  398. }