lib.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. extern crate proc_macro;
  2. use proc_macro::TokenStream;
  3. use quote::quote;
  4. use syn::parse::{Parse, ParseStream, Result};
  5. use syn::{parse_macro_input, Ident, LitInt, Token};
  6. /// This macro attribute is a helper used for defining BOLT apply proxy instructions.
  7. #[proc_macro_attribute]
  8. pub fn apply_system(attr: TokenStream, item: TokenStream) -> TokenStream {
  9. let attr_p = parse_macro_input!(attr as SystemTemplateInput);
  10. let max_components = attr_p.max_components;
  11. // Parse the original module content
  12. let mut input: syn::ItemMod = syn::parse(item).expect("Failed to parse input module");
  13. // Generate a function for execute instruction
  14. let funcs = (2..=max_components).map(|i| {
  15. let apply_func_name = syn::Ident::new(&format!("apply{}", i), proc_macro2::Span::call_site());
  16. let execute_func_name = syn::Ident::new(&format!("execute_{}", i), proc_macro2::Span::call_site());
  17. let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
  18. let updates = (1..=i).enumerate().map(|(index, n)| {
  19. let component_program_name = syn::Ident::new(&format!("component_program_{}", n), proc_macro2::Span::call_site());
  20. let bolt_component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());
  21. quote! {
  22. let update_result = bolt_component::cpi::update(
  23. build_update_context(
  24. ctx.accounts.#component_program_name.clone(),
  25. ctx.accounts.#bolt_component_name.clone(),
  26. ctx.accounts.authority.clone(),
  27. ctx.accounts.instruction_sysvar_account.clone(),
  28. ),
  29. res[#index].to_owned()
  30. )?;
  31. }
  32. });
  33. quote! {
  34. pub fn #apply_func_name<'info>(ctx: Context<'_, '_, '_, 'info, #data_struct<'info>>, args: Vec<u8>) -> Result<()> {
  35. if !ctx.accounts.authority.is_signer && ctx.accounts.authority.key != &ID {
  36. return Err(WorldError::InvalidAuthority.into());
  37. }
  38. let remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
  39. let res = bolt_system::cpi::#execute_func_name(
  40. ctx.accounts
  41. .build()
  42. .with_remaining_accounts(remaining_accounts),args)?.get().to_vec();
  43. #(#updates)*
  44. Ok(())
  45. }
  46. }
  47. });
  48. // Append each generated function to the module's items
  49. if let Some((brace, mut content)) = input.content.take() {
  50. for func in funcs {
  51. let parsed_func: syn::Item =
  52. syn::parse2(func).expect("Failed to parse generated function");
  53. content.push(parsed_func);
  54. }
  55. input.content = Some((brace, content));
  56. }
  57. let data_def = (2..=max_components).map(|i| {
  58. let data_struct =
  59. syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
  60. let fields = (1..=i).map(|n| {
  61. let component_program_name = syn::Ident::new(
  62. &format!("component_program_{}", n),
  63. proc_macro2::Span::call_site(),
  64. );
  65. let component_name = syn::Ident::new(
  66. &format!("bolt_component_{}", n),
  67. proc_macro2::Span::call_site(),
  68. );
  69. quote! {
  70. /// CHECK: bolt component program check
  71. pub #component_program_name: UncheckedAccount<'info>,
  72. #[account(mut)]
  73. /// CHECK: component account
  74. pub #component_name: UncheckedAccount<'info>,
  75. }
  76. });
  77. let struct_def = quote! {
  78. #[derive(Accounts)]
  79. pub struct #data_struct<'info> {
  80. /// CHECK: bolt system program check
  81. pub bolt_system: UncheckedAccount<'info>,
  82. #(#fields)*
  83. /// CHECK: authority check
  84. pub authority: Signer<'info>,
  85. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  86. /// CHECK: instruction sysvar check
  87. pub instruction_sysvar_account: UncheckedAccount<'info>,
  88. }
  89. };
  90. quote! {
  91. #struct_def
  92. }
  93. });
  94. let impl_build_def = (2..=max_components).map(|i| {
  95. let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
  96. let set_data_struct = syn::Ident::new(&format!("SetData{}", i), proc_macro2::Span::call_site());
  97. let fields: Vec<_> = (1..=i).map(|n| {
  98. let component_key = syn::Ident::new(&format!("component{}", n), proc_macro2::Span::call_site());
  99. let component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());
  100. quote! {
  101. #component_key: self.#component_name.to_account_info(),
  102. }
  103. }).collect();
  104. quote! {
  105. impl<'info> #data_struct<'info> {
  106. pub fn build(&self) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::#set_data_struct<'info>> {
  107. let cpi_program = self.bolt_system.to_account_info();
  108. let cpi_accounts = bolt_system::cpi::accounts::#set_data_struct {
  109. #(#fields)*
  110. authority: self.authority.to_account_info(),
  111. };
  112. CpiContext::new(cpi_program, cpi_accounts)
  113. }
  114. }
  115. }
  116. });
  117. // Return the modified module
  118. let output = quote! {
  119. #input
  120. #(#data_def)*
  121. #(#impl_build_def)*
  122. };
  123. output.into()
  124. }
  125. // Define a struct to parse macro input
  126. struct SystemTemplateInput {
  127. max_components: usize,
  128. }
  129. impl Parse for SystemTemplateInput {
  130. fn parse(input: ParseStream) -> Result<Self> {
  131. let _ = input.parse::<Ident>()?;
  132. let _ = input.parse::<Token![=]>()?;
  133. let max_components: LitInt = input.parse()?;
  134. let max_value = max_components.base10_parse()?;
  135. Ok(SystemTemplateInput {
  136. max_components: max_value,
  137. })
  138. }
  139. }