accounts.rs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. use crate::{
  2. AccountField, AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral,
  3. ConstraintOwner, ConstraintRentExempt, ConstraintSigner, Field, Ty,
  4. };
  5. use quote::quote;
  6. pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
  7. // Deserialization for each field.
  8. let deser_fields: Vec<proc_macro2::TokenStream> = accs
  9. .fields
  10. .iter()
  11. .map(|af: &AccountField| match af {
  12. AccountField::AccountsStruct(s) => {
  13. let name = &s.ident;
  14. quote! {
  15. let #name = Accounts::try_accounts(program_id, accounts)?;
  16. }
  17. }
  18. AccountField::Field(f) => {
  19. let name = f.typed_ident();
  20. match f.is_init {
  21. false => quote! {
  22. let #name = Accounts::try_accounts(program_id, accounts)?;
  23. },
  24. true => quote! {
  25. let #name = AccountsInit::try_accounts_init(program_id, accounts)?;
  26. },
  27. }
  28. }
  29. })
  30. .collect();
  31. // Constraint checks for each account fields.
  32. let access_checks: Vec<proc_macro2::TokenStream> = accs
  33. .fields
  34. .iter()
  35. // TODO: allow constraints on composite fields.
  36. .filter_map(|af: &AccountField| match af {
  37. AccountField::AccountsStruct(_) => None,
  38. AccountField::Field(f) => Some(f),
  39. })
  40. .map(|f: &Field| {
  41. let checks: Vec<proc_macro2::TokenStream> = f
  42. .constraints
  43. .iter()
  44. .map(|c| generate_constraint(&f, c))
  45. .collect();
  46. quote! {
  47. #(#checks)*
  48. }
  49. })
  50. .collect();
  51. // Each field in the final deserialized accounts struct.
  52. let return_tys: Vec<proc_macro2::TokenStream> = accs
  53. .fields
  54. .iter()
  55. .map(|f: &AccountField| {
  56. let name = match f {
  57. AccountField::AccountsStruct(s) => &s.ident,
  58. AccountField::Field(f) => &f.ident,
  59. };
  60. quote! {
  61. #name
  62. }
  63. })
  64. .collect();
  65. // Exit program code-blocks for each account.
  66. let on_save: Vec<proc_macro2::TokenStream> = accs
  67. .fields
  68. .iter()
  69. .map(|af: &AccountField| {
  70. match af {
  71. AccountField::AccountsStruct(s) => {
  72. let name = &s.ident;
  73. quote! {
  74. self.#name.exit(program_id)?;
  75. }
  76. }
  77. AccountField::Field(f) => {
  78. let ident = &f.ident;
  79. let info = match f.ty {
  80. // Only ProgramAccounts are automatically saved (when
  81. // marked `#[account(mut)]`).
  82. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  83. _ => return quote! {},
  84. };
  85. match f.is_mut {
  86. false => quote! {},
  87. true => quote! {
  88. // Only persist the change if the account is owned by the
  89. // current program.
  90. if program_id == self.#info.owner {
  91. let info = self.#info;
  92. let mut data = info.try_borrow_mut_data()?;
  93. let dst: &mut [u8] = &mut data;
  94. let mut cursor = std::io::Cursor::new(dst);
  95. self.#ident.try_serialize(&mut cursor)?;
  96. }
  97. },
  98. }
  99. }
  100. }
  101. })
  102. .collect();
  103. // Implementation for `ToAccountInfos` trait.
  104. let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
  105. .fields
  106. .iter()
  107. .map(|f: &AccountField| {
  108. let name = match f {
  109. AccountField::AccountsStruct(s) => &s.ident,
  110. AccountField::Field(f) => &f.ident,
  111. };
  112. quote! {
  113. account_infos.extend(self.#name.to_account_infos());
  114. }
  115. })
  116. .collect();
  117. // Implementation for `ToAccountMetas` trait.
  118. let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
  119. .fields
  120. .iter()
  121. .map(|f: &AccountField| {
  122. let name = match f {
  123. AccountField::AccountsStruct(s) => &s.ident,
  124. AccountField::Field(f) => &f.ident,
  125. };
  126. quote! {
  127. account_metas.extend(self.#name.to_account_metas());
  128. }
  129. })
  130. .collect();
  131. let name = &accs.ident;
  132. let (combined_generics, trait_generics, strct_generics) = match accs.generics.lt_token {
  133. None => (quote! {<'info>}, quote! {<'info>}, quote! {}),
  134. Some(_) => {
  135. let g = &accs.generics;
  136. (quote! {#g}, quote! {#g}, quote! {#g})
  137. }
  138. };
  139. quote! {
  140. impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
  141. fn try_accounts(program_id: &solana_program::pubkey::Pubkey, accounts: &mut &[solana_program::account_info::AccountInfo<'info>]) -> Result<Self, solana_program::program_error::ProgramError> {
  142. // Deserialize each account.
  143. #(#deser_fields)*
  144. // Perform constraint checks on each account.
  145. #(#access_checks)*
  146. // Success. Return the validated accounts.
  147. Ok(#name {
  148. #(#return_tys),*
  149. })
  150. }
  151. }
  152. impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
  153. fn to_account_infos(&self) -> Vec<solana_program::account_info::AccountInfo<'info>> {
  154. let mut account_infos = vec![];
  155. #(#to_acc_infos)*
  156. account_infos
  157. }
  158. }
  159. impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
  160. fn to_account_metas(&self) -> Vec<solana_program::instruction::AccountMeta> {
  161. let mut account_metas = vec![];
  162. #(#to_acc_metas)*
  163. account_metas
  164. }
  165. }
  166. impl#strct_generics #name#strct_generics {
  167. pub fn exit(&self, program_id: &solana_program::pubkey::Pubkey) -> solana_program::entrypoint::ProgramResult {
  168. #(#on_save)*
  169. Ok(())
  170. }
  171. }
  172. }
  173. }
  174. pub fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
  175. match c {
  176. Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
  177. Constraint::Signer(c) => generate_constraint_signer(f, c),
  178. Constraint::Literal(c) => generate_constraint_literal(f, c),
  179. Constraint::Owner(c) => generate_constraint_owner(f, c),
  180. Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
  181. }
  182. }
  183. pub fn generate_constraint_belongs_to(
  184. f: &Field,
  185. c: &ConstraintBelongsTo,
  186. ) -> proc_macro2::TokenStream {
  187. // todo: assert the field type.
  188. let target = c.join_target.clone();
  189. let ident = &f.ident;
  190. // todo: would be nice if target could be an account info object.
  191. quote! {
  192. if &#ident.#target != #target.to_account_info().key {
  193. return Err(ProgramError::Custom(1)); // todo: error codes
  194. }
  195. }
  196. }
  197. pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
  198. let ident = &f.ident;
  199. let info = match f.ty {
  200. Ty::AccountInfo => quote! { #ident },
  201. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  202. _ => panic!("Invalid syntax: signer cannot be specified."),
  203. };
  204. quote! {
  205. if !#info.is_signer {
  206. return Err(ProgramError::MissingRequiredSignature);
  207. }
  208. }
  209. }
  210. pub fn generate_constraint_literal(_f: &Field, c: &ConstraintLiteral) -> proc_macro2::TokenStream {
  211. let tokens = &c.tokens;
  212. quote! {
  213. if !(#tokens) {
  214. return Err(ProgramError::Custom(1)); // todo: error codes
  215. }
  216. }
  217. }
  218. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  219. let ident = &f.ident;
  220. let info = match f.ty {
  221. Ty::AccountInfo => quote! { #ident },
  222. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  223. _ => panic!("Invalid syntax: owner cannot be specified."),
  224. };
  225. match c {
  226. ConstraintOwner::Skip => quote! {},
  227. ConstraintOwner::Program => quote! {
  228. if #info.owner != program_id {
  229. return Err(ProgramError::Custom(1)); // todo: error codes
  230. }
  231. },
  232. }
  233. }
  234. pub fn generate_constraint_rent_exempt(
  235. f: &Field,
  236. c: &ConstraintRentExempt,
  237. ) -> proc_macro2::TokenStream {
  238. let ident = &f.ident;
  239. let info = match f.ty {
  240. Ty::AccountInfo => quote! { #ident },
  241. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  242. _ => panic!("Invalid syntax: rent exemption cannot be specified."),
  243. };
  244. match c {
  245. ConstraintRentExempt::Skip => quote! {},
  246. ConstraintRentExempt::Enforce => quote! {
  247. if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  248. return Err(ProgramError::Custom(2)); // todo: error codes
  249. }
  250. },
  251. }
  252. }