program.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. use crate::{Program, Rpc};
  2. use heck::CamelCase;
  3. use quote::quote;
  4. pub fn generate(program: Program) -> proc_macro2::TokenStream {
  5. let mod_name = &program.name;
  6. let instruction_name = instruction_enum_name(&program);
  7. let dispatch = generate_dispatch(&program);
  8. let methods = generate_methods(&program);
  9. let instruction = generate_instruction(&program);
  10. let cpi = generate_cpi(&program);
  11. quote! {
  12. // Import everything in the mod, in case the user wants to put types
  13. // in there.
  14. use #mod_name::*;
  15. #[cfg(not(feature = "no-entrypoint"))]
  16. solana_program::entrypoint!(entry);
  17. #[cfg(not(feature = "no-entrypoint"))]
  18. fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
  19. let mut data: &[u8] = instruction_data;
  20. let ix = instruction::#instruction_name::deserialize(&mut data)
  21. .map_err(|_| ProgramError::Custom(1))?; // todo: error code
  22. #dispatch
  23. }
  24. #methods
  25. #instruction
  26. #cpi
  27. }
  28. }
  29. pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
  30. let program_name = &program.name;
  31. let dispatch_arms: Vec<proc_macro2::TokenStream> = program
  32. .rpcs
  33. .iter()
  34. .map(|rpc| {
  35. let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
  36. let variant_arm = generate_ix_variant(program, rpc);
  37. let rpc_name = &rpc.raw_method.sig.ident;
  38. let anchor = &rpc.anchor_ident;
  39. quote! {
  40. instruction::#variant_arm => {
  41. let mut remaining_accounts: &[AccountInfo] = accounts;
  42. let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
  43. #program_name::#rpc_name(
  44. Context::new(&mut accounts, program_id, remaining_accounts),
  45. #(#rpc_arg_names),*
  46. )?;
  47. accounts.exit(program_id)
  48. }
  49. }
  50. })
  51. .collect();
  52. quote! {
  53. match ix {
  54. #(#dispatch_arms),*
  55. }
  56. }
  57. }
  58. pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
  59. let enum_name = instruction_enum_name(program);
  60. let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
  61. let rpc_name_camel = proc_macro2::Ident::new(
  62. &rpc.raw_method.sig.ident.to_string().to_camel_case(),
  63. rpc.raw_method.sig.ident.span(),
  64. );
  65. if rpc.args.len() == 0 {
  66. quote! {
  67. #enum_name::#rpc_name_camel
  68. }
  69. } else {
  70. quote! {
  71. #enum_name::#rpc_name_camel {
  72. #(#rpc_arg_names),*
  73. }
  74. }
  75. }
  76. }
  77. pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
  78. let program_mod = &program.program_mod;
  79. quote! {
  80. #program_mod
  81. }
  82. }
  83. pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
  84. let enum_name = instruction_enum_name(program);
  85. let variants: Vec<proc_macro2::TokenStream> = program
  86. .rpcs
  87. .iter()
  88. .map(|rpc| {
  89. let rpc_name_camel = proc_macro2::Ident::new(
  90. &rpc.raw_method.sig.ident.to_string().to_camel_case(),
  91. rpc.raw_method.sig.ident.span(),
  92. );
  93. let raw_args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
  94. // If no args, output a "unit" variant instead of a struct variant.
  95. if rpc.args.len() == 0 {
  96. quote! {
  97. #rpc_name_camel
  98. }
  99. } else {
  100. quote! {
  101. #rpc_name_camel {
  102. #(#raw_args),*
  103. }
  104. }
  105. }
  106. })
  107. .collect();
  108. quote! {
  109. pub mod instruction {
  110. use super::*;
  111. #[derive(AnchorSerialize, AnchorDeserialize)]
  112. pub enum #enum_name {
  113. #(#variants),*
  114. }
  115. }
  116. }
  117. }
  118. fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
  119. proc_macro2::Ident::new(
  120. &format!("_{}Instruction", program.name.to_string().to_camel_case()),
  121. program.name.span(),
  122. )
  123. }
  124. fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
  125. let cpi_methods: Vec<proc_macro2::TokenStream> = program
  126. .rpcs
  127. .iter()
  128. .map(|rpc| {
  129. let accounts_ident = &rpc.anchor_ident;
  130. let cpi_method = {
  131. let ix_variant = generate_ix_variant(program, rpc);
  132. let method_name = &rpc.ident;
  133. let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
  134. quote! {
  135. pub fn #method_name<'a, 'b, 'c, 'info>(
  136. ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
  137. #(#args),*
  138. ) -> ProgramResult {
  139. let ix = {
  140. let ix = instruction::#ix_variant;
  141. let data = AnchorSerialize::try_to_vec(&ix)
  142. .map_err(|_| ProgramError::InvalidInstructionData)?;
  143. let accounts = ctx.accounts.to_account_metas();
  144. solana_program::instruction::Instruction {
  145. program_id: *ctx.program.key,
  146. accounts,
  147. data,
  148. }
  149. };
  150. let mut acc_infos = ctx.accounts.to_account_infos();
  151. acc_infos.push(ctx.program.clone());
  152. solana_sdk::program::invoke_signed(
  153. &ix,
  154. &acc_infos,
  155. ctx.signer_seeds,
  156. )
  157. }
  158. }
  159. };
  160. cpi_method
  161. })
  162. .collect();
  163. quote! {
  164. pub mod cpi {
  165. use super::*;
  166. #(#cpi_methods)*
  167. }
  168. }
  169. }