lib.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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(ctx: Context<#data_struct>, 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 res = bolt_system::cpi::#execute_func_name(ctx.accounts.build(), args)?.get().to_vec();
  39. #(#updates)*
  40. Ok(())
  41. }
  42. }
  43. });
  44. // Append each generated function to the module's items
  45. if let Some((brace, mut content)) = input.content.take() {
  46. for func in funcs {
  47. let parsed_func: syn::Item =
  48. syn::parse2(func).expect("Failed to parse generated function");
  49. content.push(parsed_func);
  50. }
  51. input.content = Some((brace, content));
  52. }
  53. let data_def = (2..=max_components).map(|i| {
  54. let data_struct =
  55. syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
  56. let fields = (1..=i).map(|n| {
  57. let component_program_name = syn::Ident::new(
  58. &format!("component_program_{}", n),
  59. proc_macro2::Span::call_site(),
  60. );
  61. let component_name = syn::Ident::new(
  62. &format!("bolt_component_{}", n),
  63. proc_macro2::Span::call_site(),
  64. );
  65. quote! {
  66. /// CHECK: bolt component program check
  67. pub #component_program_name: UncheckedAccount<'info>,
  68. #[account(mut)]
  69. /// CHECK: component account
  70. pub #component_name: UncheckedAccount<'info>,
  71. }
  72. });
  73. let struct_def = quote! {
  74. #[derive(Accounts)]
  75. pub struct #data_struct<'info> {
  76. /// CHECK: bolt system program check
  77. pub bolt_system: UncheckedAccount<'info>,
  78. #(#fields)*
  79. /// CHECK: authority check
  80. pub authority: UncheckedAccount<'info>,
  81. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  82. /// CHECK: instruction sysvar check
  83. pub instruction_sysvar_account: UncheckedAccount<'info>,
  84. }
  85. };
  86. quote! {
  87. #struct_def
  88. }
  89. });
  90. let impl_build_def = (2..=max_components).map(|i| {
  91. let data_struct = syn::Ident::new(&format!("ApplySystem{}", i), proc_macro2::Span::call_site());
  92. let set_data_struct = syn::Ident::new(&format!("SetData{}", i), proc_macro2::Span::call_site());
  93. let fields: Vec<_> = (1..=i).map(|n| {
  94. let component_key = syn::Ident::new(&format!("component{}", n), proc_macro2::Span::call_site());
  95. let component_name = syn::Ident::new(&format!("bolt_component_{}", n), proc_macro2::Span::call_site());
  96. quote! {
  97. #component_key: self.#component_name.to_account_info(),
  98. }
  99. }).collect();
  100. quote! {
  101. impl<'info> #data_struct<'info> {
  102. pub fn build(&self) -> CpiContext<'_, '_, '_, 'info, bolt_system::cpi::accounts::#set_data_struct<'info>> {
  103. let cpi_program = self.bolt_system.to_account_info();
  104. let cpi_accounts = bolt_system::cpi::accounts::#set_data_struct {
  105. #(#fields)*
  106. };
  107. CpiContext::new(cpi_program, cpi_accounts)
  108. }
  109. }
  110. }
  111. });
  112. // Return the modified module
  113. let output = quote! {
  114. #input
  115. #(#data_def)*
  116. #(#impl_build_def)*
  117. };
  118. output.into()
  119. }
  120. // Define a struct to parse macro input
  121. struct SystemTemplateInput {
  122. max_components: usize,
  123. }
  124. // Implement parsing for the macro input
  125. impl Parse for SystemTemplateInput {
  126. fn parse(input: ParseStream) -> Result<Self> {
  127. let _ = input.parse::<Ident>()?; // Parse the key (e.g., "max_components")
  128. let _ = input.parse::<Token![=]>()?; // Parse the '='
  129. let max_components: LitInt = input.parse()?; // Parse the value
  130. let max_value = max_components.base10_parse()?;
  131. Ok(SystemTemplateInput {
  132. max_components: max_value,
  133. })
  134. }
  135. }