accounts.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  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. let field = match &f.ty {
  371. Ty::Loader(_) => quote! {#ident.load()?},
  372. _ => quote! {#ident},
  373. };
  374. quote! {
  375. if &#field.#target != #target.to_account_info().key {
  376. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  377. }
  378. }
  379. }
  380. pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
  381. let ident = &f.ident;
  382. let info = match f.ty {
  383. Ty::AccountInfo => quote! { #ident },
  384. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  385. _ => panic!("Invalid syntax: signer cannot be specified."),
  386. };
  387. quote! {
  388. // Don't enforce on CPI, since usually a program is signing and so
  389. // the `try_accounts` deserializatoin will fail *if* the one
  390. // tries to manually invoke it.
  391. //
  392. // This check will be performed on the other end of the invocation.
  393. if cfg!(not(feature = "cpi")) {
  394. if !#info.is_signer {
  395. return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
  396. }
  397. }
  398. }
  399. }
  400. pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
  401. let tokens = &c.tokens;
  402. quote! {
  403. if !(#tokens) {
  404. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
  405. }
  406. }
  407. }
  408. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  409. let ident = &f.ident;
  410. let owner_target = c.owner_target.clone();
  411. quote! {
  412. if #ident.to_account_info().owner != #owner_target.to_account_info().key {
  413. return Err(ProgramError::Custom(76)); // todo: proper error.
  414. }
  415. }
  416. }
  417. pub fn generate_constraint_rent_exempt(
  418. f: &Field,
  419. c: &ConstraintRentExempt,
  420. ) -> proc_macro2::TokenStream {
  421. let ident = &f.ident;
  422. let info = match f.ty {
  423. Ty::AccountInfo => quote! { #ident },
  424. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  425. Ty::Loader(_) => quote! { #ident.to_account_info() },
  426. _ => panic!("Invalid syntax: rent exemption cannot be specified."),
  427. };
  428. match c {
  429. ConstraintRentExempt::Skip => quote! {},
  430. ConstraintRentExempt::Enforce => quote! {
  431. if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  432. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
  433. }
  434. },
  435. }
  436. }
  437. pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2::TokenStream {
  438. let name = &f.ident;
  439. let seeds = &c.seeds;
  440. quote! {
  441. let program_signer = Pubkey::create_program_address(
  442. &#seeds,
  443. program_id,
  444. ).map_err(|_| anchor_lang::solana_program::program_error::ProgramError::Custom(1))?; // todo
  445. if #name.to_account_info().key != &program_signer {
  446. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
  447. }
  448. }
  449. }
  450. pub fn generate_constraint_executable(
  451. f: &Field,
  452. _c: &ConstraintExecutable,
  453. ) -> proc_macro2::TokenStream {
  454. let name = &f.ident;
  455. quote! {
  456. if !#name.to_account_info().executable {
  457. return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(5)) // todo
  458. }
  459. }
  460. }
  461. pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
  462. let program_target = c.program_target.clone();
  463. let ident = &f.ident;
  464. let account_ty = match &f.ty {
  465. Ty::CpiState(ty) => &ty.account_ident,
  466. _ => panic!("Invalid state constraint"),
  467. };
  468. quote! {
  469. // Checks the given state account is the canonical state account for
  470. // the target program.
  471. if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
  472. return Err(ProgramError::Custom(1)); // todo: proper error.
  473. }
  474. if #ident.to_account_info().owner != #program_target.to_account_info().key {
  475. return Err(ProgramError::Custom(1)); // todo: proper error.
  476. }
  477. }
  478. }
  479. pub fn generate_constraint_associated(
  480. f: &Field,
  481. c: &ConstraintAssociated,
  482. ) -> proc_macro2::TokenStream {
  483. let associated_target = c.associated_target.clone();
  484. let field = &f.ident;
  485. let (account_ty, is_zero_copy) = match &f.ty {
  486. Ty::ProgramAccount(ty) => (&ty.account_ident, false),
  487. Ty::Loader(ty) => (&ty.account_ident, true),
  488. _ => panic!("Invalid associated constraint"),
  489. };
  490. let space = match &f.space {
  491. // If no explicit space param was given, serialize the type to bytes
  492. // and take the length (with +8 for the discriminator.)
  493. None => match is_zero_copy {
  494. false => {
  495. quote! {
  496. let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
  497. }
  498. }
  499. true => {
  500. quote! {
  501. let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
  502. }
  503. }
  504. },
  505. // Explicit account size given. Use it.
  506. Some(s) => quote! {
  507. let space = #s;
  508. },
  509. };
  510. let payer = match &f.payer {
  511. None => quote! {
  512. let payer = #associated_target.to_account_info();
  513. },
  514. Some(p) => quote! {
  515. let payer = #p.to_account_info();
  516. },
  517. };
  518. let seeds_no_nonce = match f.associated_seeds.len() {
  519. 0 => quote! {
  520. [
  521. &b"anchor"[..],
  522. #associated_target.to_account_info().key.as_ref(),
  523. ]
  524. },
  525. _ => {
  526. let seeds = to_seeds_tts(&f.associated_seeds);
  527. quote! {
  528. [
  529. &b"anchor"[..],
  530. #associated_target.to_account_info().key.as_ref(),
  531. #seeds
  532. ]
  533. }
  534. }
  535. };
  536. let seeds_with_nonce = match f.associated_seeds.len() {
  537. 0 => quote! {
  538. [
  539. &b"anchor"[..],
  540. #associated_target.to_account_info().key.as_ref(),
  541. &[nonce],
  542. ]
  543. },
  544. _ => {
  545. let seeds = to_seeds_tts(&f.associated_seeds);
  546. quote! {
  547. [
  548. &b"anchor"[..],
  549. #associated_target.to_account_info().key.as_ref(),
  550. #seeds
  551. &[nonce],
  552. ]
  553. }
  554. }
  555. };
  556. let account_wrapper_ty = match is_zero_copy {
  557. false => quote! {
  558. anchor_lang::ProgramAccount
  559. },
  560. true => quote! {
  561. anchor_lang::Loader
  562. },
  563. };
  564. let nonce_assignment = match is_zero_copy {
  565. false => quote! {},
  566. // Zero copy is not deserialized, so the data must be lazy loaded.
  567. true => quote! {
  568. .load_init()?
  569. },
  570. };
  571. quote! {
  572. let #field: #account_wrapper_ty<#account_ty> = {
  573. #space
  574. #payer
  575. let (associated_field, nonce) = Pubkey::find_program_address(
  576. &#seeds_no_nonce,
  577. program_id,
  578. );
  579. if &associated_field != #field.key {
  580. return Err(ProgramError::Custom(45)); // todo: proper error.
  581. }
  582. let lamports = rent.minimum_balance(space);
  583. let ix = anchor_lang::solana_program::system_instruction::create_account(
  584. payer.key,
  585. #field.key,
  586. lamports,
  587. space as u64,
  588. program_id,
  589. );
  590. let seeds = #seeds_with_nonce;
  591. let signer = &[&seeds[..]];
  592. anchor_lang::solana_program::program::invoke_signed(
  593. &ix,
  594. &[
  595. #field.clone(),
  596. payer.clone(),
  597. system_program.clone(),
  598. ],
  599. signer,
  600. ).map_err(|e| {
  601. anchor_lang::solana_program::msg!("Unable to create associated account");
  602. e
  603. })?;
  604. // For now, we assume all accounts created with the `associated`
  605. // attribute have a `nonce` field in their account.
  606. let mut pa: #account_wrapper_ty<#account_ty> = #account_wrapper_ty::try_from_init(
  607. &#field,
  608. )?;
  609. pa#nonce_assignment.__nonce = nonce;
  610. pa
  611. };
  612. }
  613. }
  614. // Returns the inner part of the seeds slice as a token stream.
  615. fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
  616. assert!(seeds.len() > 0);
  617. let seed_0 = &seeds[0];
  618. let mut tts = quote! {
  619. #seed_0.to_account_info().key.as_ref(),
  620. };
  621. for seed in &seeds[1..] {
  622. tts = quote! {
  623. #tts
  624. #seed.to_account_info().key.as_ref(),
  625. };
  626. }
  627. tts
  628. }