accounts.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. use crate::{
  2. AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
  3. ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
  4. ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, Field, Ty,
  5. };
  6. use heck::SnakeCase;
  7. use quote::quote;
  8. pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
  9. // All fields without an `#[account(associated)]` attribute.
  10. let non_associated_fields: Vec<&AccountField> =
  11. accs.fields.iter().filter(|af| !is_associated(af)).collect();
  12. // Deserialization for each field
  13. let deser_fields: Vec<proc_macro2::TokenStream> = accs
  14. .fields
  15. .iter()
  16. .map(|af: &AccountField| {
  17. match af {
  18. AccountField::AccountsStruct(s) => {
  19. let name = &s.ident;
  20. let ty = &s.raw_field.ty;
  21. quote! {
  22. let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
  23. }
  24. }
  25. AccountField::Field(f) => {
  26. // Associated fields are *first* deserialized into
  27. // AccountInfos, and then later deserialized into
  28. // ProgramAccounts in the "constraint check" phase.
  29. if is_associated(af) {
  30. let name = &f.ident;
  31. quote!{
  32. let #name = &accounts[0];
  33. *accounts = &accounts[1..];
  34. }
  35. } else {
  36. let name = &f.typed_ident();
  37. match f.is_init {
  38. false => quote! {
  39. let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
  40. },
  41. true => quote! {
  42. let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
  43. },
  44. }
  45. }
  46. }
  47. }
  48. })
  49. .collect();
  50. // Deserialization for each *associated* field. This must be after
  51. // the deser_fields.
  52. let deser_associated_fields: Vec<proc_macro2::TokenStream> = accs
  53. .fields
  54. .iter()
  55. .filter_map(|af| match af {
  56. AccountField::AccountsStruct(_s) => None,
  57. AccountField::Field(f) => match is_associated(af) {
  58. false => None,
  59. true => Some(f),
  60. },
  61. })
  62. .map(|field: &Field| {
  63. // TODO: the constraints should be sorted so that the associated
  64. // constraint comes first.
  65. let checks = field
  66. .constraints
  67. .iter()
  68. .map(|c| generate_field_constraint(&field, c))
  69. .collect::<Vec<proc_macro2::TokenStream>>();
  70. quote! {
  71. #(#checks)*
  72. }
  73. })
  74. .collect();
  75. // Constraint checks for each account fields.
  76. let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
  77. .iter()
  78. .map(|af: &&AccountField| {
  79. let checks: Vec<proc_macro2::TokenStream> = match af {
  80. AccountField::Field(f) => f
  81. .constraints
  82. .iter()
  83. .map(|c| generate_field_constraint(&f, c))
  84. .collect(),
  85. AccountField::AccountsStruct(s) => s
  86. .constraints
  87. .iter()
  88. .map(|c| generate_composite_constraint(&s, c))
  89. .collect(),
  90. };
  91. quote! {
  92. #(#checks)*
  93. }
  94. })
  95. .collect();
  96. // Each field in the final deserialized accounts struct.
  97. let return_tys: Vec<proc_macro2::TokenStream> = accs
  98. .fields
  99. .iter()
  100. .map(|f: &AccountField| {
  101. let name = match f {
  102. AccountField::AccountsStruct(s) => &s.ident,
  103. AccountField::Field(f) => &f.ident,
  104. };
  105. quote! {
  106. #name
  107. }
  108. })
  109. .collect();
  110. // Exit program code-blocks for each account.
  111. let on_save: Vec<proc_macro2::TokenStream> = accs
  112. .fields
  113. .iter()
  114. .map(|af: &AccountField| match af {
  115. AccountField::AccountsStruct(s) => {
  116. let name = &s.ident;
  117. quote! {
  118. anchor_lang::AccountsExit::exit(&self.#name, program_id)?;
  119. }
  120. }
  121. AccountField::Field(f) => {
  122. let ident = &f.ident;
  123. match f.is_mut {
  124. false => quote! {},
  125. true => quote! {
  126. anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
  127. },
  128. }
  129. }
  130. })
  131. .collect();
  132. // Implementation for `ToAccountInfos` trait.
  133. let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
  134. .fields
  135. .iter()
  136. .map(|f: &AccountField| {
  137. let name = match f {
  138. AccountField::AccountsStruct(s) => &s.ident,
  139. AccountField::Field(f) => &f.ident,
  140. };
  141. quote! {
  142. account_infos.extend(self.#name.to_account_infos());
  143. }
  144. })
  145. .collect();
  146. // Implementation for `ToAccountMetas` trait.
  147. let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
  148. .fields
  149. .iter()
  150. .map(|f: &AccountField| {
  151. let (name, is_signer) = match f {
  152. AccountField::AccountsStruct(s) => (&s.ident, quote! {None}),
  153. AccountField::Field(f) => {
  154. let is_signer = match f.is_signer {
  155. false => quote! {None},
  156. true => quote! {Some(true)},
  157. };
  158. (&f.ident, is_signer)
  159. }
  160. };
  161. quote! {
  162. account_metas.extend(self.#name.to_account_metas(#is_signer));
  163. }
  164. })
  165. .collect();
  166. let name = &accs.ident;
  167. let (combined_generics, trait_generics, strct_generics) = match accs.generics.lt_token {
  168. None => (quote! {<'info>}, quote! {<'info>}, quote! {}),
  169. Some(_) => {
  170. let g = &accs.generics;
  171. (quote! {#g}, quote! {#g}, quote! {#g})
  172. }
  173. };
  174. let account_mod_name: proc_macro2::TokenStream = format!(
  175. "__client_accounts_{}",
  176. accs.ident.to_string().to_snake_case()
  177. )
  178. .parse()
  179. .unwrap();
  180. let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
  181. .fields
  182. .iter()
  183. .map(|f: &AccountField| match f {
  184. AccountField::AccountsStruct(s) => {
  185. let name = &s.ident;
  186. let symbol: proc_macro2::TokenStream = format!(
  187. "__client_accounts_{0}::{1}",
  188. s.symbol.to_snake_case(),
  189. s.symbol,
  190. )
  191. .parse()
  192. .unwrap();
  193. quote! {
  194. pub #name: #symbol
  195. }
  196. }
  197. AccountField::Field(f) => {
  198. let name = &f.ident;
  199. quote! {
  200. pub #name: anchor_lang::solana_program::pubkey::Pubkey
  201. }
  202. }
  203. })
  204. .collect();
  205. let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
  206. .fields
  207. .iter()
  208. .map(|f: &AccountField| match f {
  209. AccountField::AccountsStruct(s) => {
  210. let name = &s.ident;
  211. quote! {
  212. account_metas.extend(self.#name.to_account_metas(None));
  213. }
  214. }
  215. AccountField::Field(f) => {
  216. let is_signer = match f.is_signer {
  217. false => quote! {false},
  218. true => quote! {true},
  219. };
  220. let meta = match f.is_mut {
  221. false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
  222. true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
  223. };
  224. let name = &f.ident;
  225. quote! {
  226. account_metas.push(#meta(self.#name, #is_signer));
  227. }
  228. }
  229. })
  230. .collect();
  231. // Re-export all composite account structs (i.e. other structs deriving
  232. // accounts embedded into this struct. Required because, these embedded
  233. // structs are *not* visible from the #[program] macro, which is responsible
  234. // for generating the `accounts` mod, which aggregates all the the generated
  235. // accounts used for structs.
  236. let re_exports: Vec<proc_macro2::TokenStream> = {
  237. // First, dedup the exports.
  238. let mut re_exports = std::collections::HashSet::new();
  239. for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
  240. AccountField::AccountsStruct(s) => Some(s),
  241. AccountField::Field(_) => None,
  242. }) {
  243. re_exports.insert(format!(
  244. "__client_accounts_{0}::{1}",
  245. f.symbol.to_snake_case(),
  246. f.symbol,
  247. ));
  248. }
  249. re_exports
  250. .iter()
  251. .map(|symbol: &String| {
  252. let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
  253. quote! {
  254. pub use #symbol;
  255. }
  256. })
  257. .collect()
  258. };
  259. quote! {
  260. /// An internal, Anchor generated module. This is used (as an
  261. /// implementation detail), to generate a struct for a given
  262. /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
  263. /// instead of an `AccountInfo`. This is useful for clients that want
  264. /// to generate a list of accounts, without explicitly knowing the
  265. /// order all the fields should be in.
  266. ///
  267. /// To access the struct in this module, one should use the sibling
  268. /// `accounts` module (also generated), which re-exports this.
  269. mod #account_mod_name {
  270. use super::*;
  271. use anchor_lang::prelude::borsh;
  272. #(#re_exports)*
  273. #[derive(anchor_lang::AnchorSerialize)]
  274. pub struct #name {
  275. #(#account_struct_fields),*
  276. }
  277. impl anchor_lang::ToAccountMetas for #name {
  278. fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
  279. let mut account_metas = vec![];
  280. #(#account_struct_metas)*
  281. account_metas
  282. }
  283. }
  284. }
  285. impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
  286. #[inline(never)]
  287. 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> {
  288. // Deserialize each account.
  289. #(#deser_fields)*
  290. // Deserialize each associated account.
  291. //
  292. // Associated accounts are treated specially, because the fields
  293. // do deserialization + constraint checks in a single go,
  294. // whereas all other fields, i.e. the `deser_fields`, first
  295. // deserialize, and then do constraint checks.
  296. #(#deser_associated_fields)*
  297. // Perform constraint checks on each account.
  298. #(#access_checks)*
  299. // Success. Return the validated accounts.
  300. Ok(#name {
  301. #(#return_tys),*
  302. })
  303. }
  304. }
  305. impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
  306. fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
  307. let mut account_infos = vec![];
  308. #(#to_acc_infos)*
  309. account_infos
  310. }
  311. }
  312. impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
  313. fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
  314. let mut account_metas = vec![];
  315. #(#to_acc_metas)*
  316. account_metas
  317. }
  318. }
  319. impl#combined_generics anchor_lang::AccountsExit#trait_generics for #name#strct_generics {
  320. fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
  321. #(#on_save)*
  322. Ok(())
  323. }
  324. }
  325. }
  326. }
  327. // Returns true if the given AccountField has an associated constraint.
  328. fn is_associated(af: &AccountField) -> bool {
  329. match af {
  330. AccountField::AccountsStruct(_s) => false,
  331. AccountField::Field(f) => f
  332. .constraints
  333. .iter()
  334. .filter(|c| match c {
  335. Constraint::Associated(_c) => true,
  336. _ => false,
  337. })
  338. .next()
  339. .is_some(),
  340. }
  341. }
  342. pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
  343. match c {
  344. Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
  345. Constraint::Signer(c) => generate_constraint_signer(f, c),
  346. Constraint::Literal(c) => generate_constraint_literal(c),
  347. Constraint::Owner(c) => generate_constraint_owner(f, c),
  348. Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
  349. Constraint::Seeds(c) => generate_constraint_seeds(f, c),
  350. Constraint::Executable(c) => generate_constraint_executable(f, c),
  351. Constraint::State(c) => generate_constraint_state(f, c),
  352. Constraint::Associated(c) => generate_constraint_associated(f, c),
  353. }
  354. }
  355. pub fn generate_composite_constraint(
  356. _f: &CompositeField,
  357. c: &Constraint,
  358. ) -> proc_macro2::TokenStream {
  359. match c {
  360. Constraint::Literal(c) => generate_constraint_literal(c),
  361. _ => panic!("Composite fields can only use literal constraints"),
  362. }
  363. }
  364. pub fn generate_constraint_belongs_to(
  365. f: &Field,
  366. c: &ConstraintBelongsTo,
  367. ) -> proc_macro2::TokenStream {
  368. let target = c.join_target.clone();
  369. let ident = &f.ident;
  370. quote! {
  371. if &#ident.#target != #target.to_account_info().key {
  372. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  373. }
  374. }
  375. }
  376. pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
  377. let ident = &f.ident;
  378. let info = match f.ty {
  379. Ty::AccountInfo => quote! { #ident },
  380. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  381. _ => panic!("Invalid syntax: signer cannot be specified."),
  382. };
  383. quote! {
  384. // Don't enforce on CPI, since usually a program is signing and so
  385. // the `try_accounts` deserializatoin will fail *if* the one
  386. // tries to manually invoke it.
  387. //
  388. // This check will be performed on the other end of the invocation.
  389. if cfg!(not(feature = "cpi")) {
  390. if !#info.is_signer {
  391. return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
  392. }
  393. }
  394. }
  395. }
  396. pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
  397. let tokens = &c.tokens;
  398. quote! {
  399. if !(#tokens) {
  400. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  401. }
  402. }
  403. }
  404. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  405. let ident = &f.ident;
  406. let owner_target = c.owner_target.clone();
  407. quote! {
  408. if #ident.to_account_info().owner != #owner_target.to_account_info().key {
  409. return Err(ProgramError::Custom(76)); // todo: proper error.
  410. }
  411. }
  412. }
  413. pub fn generate_constraint_rent_exempt(
  414. f: &Field,
  415. c: &ConstraintRentExempt,
  416. ) -> proc_macro2::TokenStream {
  417. let ident = &f.ident;
  418. let info = match f.ty {
  419. Ty::AccountInfo => quote! { #ident },
  420. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  421. _ => panic!("Invalid syntax: rent exemption cannot be specified."),
  422. };
  423. match c {
  424. ConstraintRentExempt::Skip => quote! {},
  425. ConstraintRentExempt::Enforce => quote! {
  426. if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  427. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
  428. }
  429. },
  430. }
  431. }
  432. pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2::TokenStream {
  433. let name = &f.ident;
  434. let seeds = &c.seeds;
  435. quote! {
  436. let program_signer = Pubkey::create_program_address(
  437. &#seeds,
  438. program_id,
  439. ).map_err(|_| anchor_lang::solana_program::program_error::ProgramError::Custom(1))?; // todo
  440. if #name.to_account_info().key != &program_signer {
  441. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
  442. }
  443. }
  444. }
  445. pub fn generate_constraint_executable(
  446. f: &Field,
  447. _c: &ConstraintExecutable,
  448. ) -> proc_macro2::TokenStream {
  449. let name = &f.ident;
  450. quote! {
  451. if !#name.to_account_info().executable {
  452. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(5)) // todo
  453. }
  454. }
  455. }
  456. pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
  457. let program_target = c.program_target.clone();
  458. let ident = &f.ident;
  459. let account_ty = match &f.ty {
  460. Ty::CpiState(ty) => &ty.account_ident,
  461. _ => panic!("Invalid syntax"),
  462. };
  463. quote! {
  464. // Checks the given state account is the canonical state account for
  465. // the target program.
  466. if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
  467. return Err(ProgramError::Custom(1)); // todo: proper error.
  468. }
  469. if #ident.to_account_info().owner != #program_target.to_account_info().key {
  470. return Err(ProgramError::Custom(1)); // todo: proper error.
  471. }
  472. }
  473. }
  474. pub fn generate_constraint_associated(
  475. f: &Field,
  476. c: &ConstraintAssociated,
  477. ) -> proc_macro2::TokenStream {
  478. let associated_target = c.associated_target.clone();
  479. let field = &f.ident;
  480. let account_ty = match &f.ty {
  481. Ty::ProgramAccount(ty) => &ty.account_ident,
  482. _ => panic!("Invalid syntax"),
  483. };
  484. let space = match &f.space {
  485. None => quote! {
  486. let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
  487. },
  488. Some(s) => quote! {
  489. let space = #s;
  490. },
  491. };
  492. let payer = match &f.payer {
  493. None => quote! {
  494. let payer = #associated_target.to_account_info();
  495. },
  496. Some(p) => quote! {
  497. let payer = #p.to_account_info();
  498. },
  499. };
  500. let seeds_no_nonce = match f.associated_seeds.len() {
  501. 0 => quote! {
  502. [
  503. &b"anchor"[..],
  504. #associated_target.to_account_info().key.as_ref(),
  505. ]
  506. },
  507. _ => {
  508. let seeds = to_seeds_tts(&f.associated_seeds);
  509. quote! {
  510. [
  511. &b"anchor"[..],
  512. #associated_target.to_account_info().key.as_ref(),
  513. #seeds
  514. ]
  515. }
  516. }
  517. };
  518. let seeds_with_nonce = match f.associated_seeds.len() {
  519. 0 => quote! {
  520. [
  521. &b"anchor"[..],
  522. #associated_target.to_account_info().key.as_ref(),
  523. &[nonce],
  524. ]
  525. },
  526. _ => {
  527. let seeds = to_seeds_tts(&f.associated_seeds);
  528. quote! {
  529. [
  530. &b"anchor"[..],
  531. #associated_target.to_account_info().key.as_ref(),
  532. #seeds
  533. &[nonce],
  534. ]
  535. }
  536. }
  537. };
  538. quote! {
  539. let #field: anchor_lang::ProgramAccount<#account_ty> = {
  540. #space
  541. #payer
  542. let (associated_field, nonce) = Pubkey::find_program_address(
  543. &#seeds_no_nonce,
  544. program_id,
  545. );
  546. if &associated_field != #field.key {
  547. return Err(ProgramError::Custom(45)); // todo: proper error.
  548. }
  549. let lamports = rent.minimum_balance(space);
  550. let ix = anchor_lang::solana_program::system_instruction::create_account(
  551. payer.key,
  552. #field.key,
  553. lamports,
  554. space as u64,
  555. program_id,
  556. );
  557. let seeds = #seeds_with_nonce;
  558. let signer = &[&seeds[..]];
  559. anchor_lang::solana_program::program::invoke_signed(
  560. &ix,
  561. &[
  562. #field.clone(),
  563. payer.clone(),
  564. system_program.clone(),
  565. ],
  566. signer,
  567. ).map_err(|e| {
  568. anchor_lang::solana_program::msg!("Unable to create associated account");
  569. e
  570. })?;
  571. // For now, we assume all accounts created with the `associated`
  572. // attribute have a `nonce` field in their account.
  573. let mut pa: anchor_lang::ProgramAccount<#account_ty> = anchor_lang::ProgramAccount::try_from_init(
  574. &#field,
  575. )?;
  576. pa.__nonce = nonce;
  577. pa
  578. };
  579. }
  580. }
  581. // Returns the inner part of the seeds slice as a token stream.
  582. fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
  583. assert!(seeds.len() > 0);
  584. let seed_0 = &seeds[0];
  585. let mut tts = quote! {
  586. #seed_0.to_account_info().key.as_ref(),
  587. };
  588. for seed in &seeds[1..] {
  589. tts = quote! {
  590. #tts
  591. #seed.to_account_info().key.as_ref(),
  592. };
  593. }
  594. tts
  595. }