lib.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. use proc_macro::TokenStream;
  2. use proc_macro2::TokenStream as TokenStream2;
  3. use quote::{quote, ToTokens};
  4. use syn::{
  5. parse_macro_input, parse_quote, Attribute, AttributeArgs, Field, Fields, ItemMod, ItemStruct,
  6. NestedMeta, Type,
  7. };
  8. /// This macro attribute is used to define a BOLT component.
  9. ///
  10. /// Bolt components are themselves programs that can be called by other programs.
  11. ///
  12. /// # Example
  13. /// ```ignore
  14. /// #[bolt_program(Position)]
  15. /// #[program]
  16. /// pub mod component_position {
  17. /// use super::*;
  18. /// }
  19. ///
  20. /// #[account]
  21. /// #[component]
  22. /// pub struct Position {
  23. /// pub x: i64,
  24. /// pub y: i64,
  25. /// pub z: i64,
  26. /// }
  27. /// ```
  28. #[proc_macro_attribute]
  29. pub fn bolt_program(args: TokenStream, input: TokenStream) -> TokenStream {
  30. let ast = parse_macro_input!(input as syn::ItemMod);
  31. let args = parse_macro_input!(args as syn::AttributeArgs);
  32. let component_type =
  33. extract_type_name(&args).expect("Expected a component type in macro arguments");
  34. let modified = modify_component_module(ast, &component_type);
  35. let additional_macro: Attribute = parse_quote! { #[program] };
  36. TokenStream::from(quote! {
  37. #additional_macro
  38. #modified
  39. })
  40. }
  41. /// Modifies the component module and adds the necessary functions and structs.
  42. fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMod {
  43. let (initialize_fn, initialize_struct) = generate_initialize(component_type);
  44. //let (apply_fn, apply_struct, apply_impl, update_fn, update_struct) = generate_instructions(component_type);
  45. let (update_fn, update_struct) = generate_update(component_type);
  46. module.content = module.content.map(|(brace, mut items)| {
  47. items.extend(
  48. vec![initialize_fn, initialize_struct, update_fn, update_struct]
  49. .into_iter()
  50. .map(|item| syn::parse2(item).unwrap())
  51. .collect::<Vec<_>>(),
  52. );
  53. let modified_items = items
  54. .into_iter()
  55. .map(|item| match item {
  56. syn::Item::Struct(mut struct_item) if struct_item.ident == "Apply" => {
  57. modify_apply_struct(&mut struct_item);
  58. syn::Item::Struct(struct_item)
  59. }
  60. _ => item,
  61. })
  62. .collect();
  63. (brace, modified_items)
  64. });
  65. module
  66. }
  67. /// Extracts the type name from attribute arguments.
  68. fn extract_type_name(args: &AttributeArgs) -> Option<Type> {
  69. args.iter().find_map(|arg| {
  70. if let NestedMeta::Meta(syn::Meta::Path(path)) = arg {
  71. Some(Type::Path(syn::TypePath {
  72. qself: None,
  73. path: path.clone(),
  74. }))
  75. } else {
  76. None
  77. }
  78. })
  79. }
  80. /// Modifies the Apply struct, change the bolt system to accept any compatible system.
  81. fn modify_apply_struct(struct_item: &mut ItemStruct) {
  82. if let Fields::Named(fields_named) = &mut struct_item.fields {
  83. fields_named
  84. .named
  85. .iter_mut()
  86. .filter(|field| is_expecting_program(field))
  87. .for_each(|field| {
  88. field.ty = syn::parse_str("UncheckedAccount<'info>").expect("Failed to parse type");
  89. field.attrs.push(create_check_attribute());
  90. });
  91. }
  92. }
  93. /// Creates the check attribute.
  94. fn create_check_attribute() -> Attribute {
  95. parse_quote! {
  96. #[doc = "CHECK: This program can modify the data of the component"]
  97. }
  98. }
  99. /// Generates the initialize function and struct.
  100. fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
  101. (
  102. quote! {
  103. #[automatically_derived]
  104. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  105. let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
  106. 0, &ctx.accounts.instruction_sysvar_account.to_account_info()
  107. ).unwrap();
  108. if instruction.program_id != World::id() {
  109. return Err(BoltError::InvalidCaller.into());
  110. }
  111. ctx.accounts.data.set_inner(<#component_type>::default());
  112. ctx.accounts.data.bolt_metadata.authority = *ctx.accounts.authority.key;
  113. Ok(())
  114. }
  115. },
  116. quote! {
  117. #[automatically_derived]
  118. #[derive(Accounts)]
  119. pub struct Initialize<'info> {
  120. #[account(mut)]
  121. pub payer: Signer<'info>,
  122. #[account(init_if_needed, payer = payer, space = <#component_type>::size(), seeds = [<#component_type>::seed(), entity.key().as_ref()], bump)]
  123. pub data: Account<'info, #component_type>,
  124. #[account()]
  125. pub entity: Account<'info, Entity>,
  126. #[account()]
  127. pub authority: AccountInfo<'info>,
  128. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  129. pub instruction_sysvar_account: UncheckedAccount<'info>,
  130. pub system_program: Program<'info, System>,
  131. }
  132. },
  133. )
  134. }
  135. /// Generates the instructions and related structs to inject in the component.
  136. fn generate_update(component_type: &Type) -> (TokenStream2, TokenStream2) {
  137. (
  138. quote! {
  139. #[automatically_derived]
  140. pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
  141. // Check if the instruction is called from the world program
  142. let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
  143. 0, &ctx.accounts.instruction_sysvar_account.to_account_info()
  144. ).unwrap();
  145. if instruction.program_id != World::id() {
  146. return Err(BoltError::InvalidCaller.into());
  147. }
  148. // Check if the authority is authorized to modify the data
  149. if ctx.accounts.bolt_component.bolt_metadata.authority != World::id() && ctx.accounts.bolt_component.bolt_metadata.authority != *ctx.accounts.authority.key {
  150. return Err(BoltError::InvalidAuthority.into());
  151. }
  152. ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
  153. Ok(())
  154. }
  155. },
  156. quote! {
  157. #[automatically_derived]
  158. #[derive(Accounts)]
  159. pub struct Update<'info> {
  160. #[account(mut)]
  161. pub bolt_component: Account<'info, #component_type>,
  162. #[account()]
  163. /// CHECK: The authority of the component
  164. pub authority: AccountInfo<'info>,
  165. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  166. pub instruction_sysvar_account: UncheckedAccount<'info>,
  167. }
  168. },
  169. )
  170. }
  171. /// Checks if the field is expecting a program.
  172. fn is_expecting_program(field: &Field) -> bool {
  173. field.ty.to_token_stream().to_string().contains("Program")
  174. }