lib.rs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. ///
  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_with_session_fn, update_struct, update_with_session_struct) =
  46. generate_update(component_type);
  47. module.content = module.content.map(|(brace, mut items)| {
  48. items.extend(
  49. vec![
  50. initialize_fn,
  51. initialize_struct,
  52. update_fn,
  53. update_struct,
  54. update_with_session_fn,
  55. update_with_session_struct,
  56. ]
  57. .into_iter()
  58. .map(|item| syn::parse2(item).unwrap())
  59. .collect::<Vec<_>>(),
  60. );
  61. let modified_items = items
  62. .into_iter()
  63. .map(|item| match item {
  64. syn::Item::Struct(mut struct_item)
  65. if struct_item.ident == "Apply" || struct_item.ident == "ApplyWithSession" =>
  66. {
  67. modify_apply_struct(&mut struct_item);
  68. syn::Item::Struct(struct_item)
  69. }
  70. _ => item,
  71. })
  72. .collect();
  73. (brace, modified_items)
  74. });
  75. module
  76. }
  77. /// Extracts the type name from attribute arguments.
  78. fn extract_type_name(args: &AttributeArgs) -> Option<Type> {
  79. args.iter().find_map(|arg| {
  80. if let NestedMeta::Meta(syn::Meta::Path(path)) = arg {
  81. Some(Type::Path(syn::TypePath {
  82. qself: None,
  83. path: path.clone(),
  84. }))
  85. } else {
  86. None
  87. }
  88. })
  89. }
  90. /// Modifies the Apply struct, change the bolt system to accept any compatible system.
  91. fn modify_apply_struct(struct_item: &mut ItemStruct) {
  92. if let Fields::Named(fields_named) = &mut struct_item.fields {
  93. fields_named
  94. .named
  95. .iter_mut()
  96. .filter(|field| is_expecting_program(field))
  97. .for_each(|field| {
  98. field.ty = syn::parse_str("UncheckedAccount<'info>").expect("Failed to parse type");
  99. field.attrs.push(create_check_attribute());
  100. });
  101. }
  102. }
  103. /// Creates the check attribute.
  104. fn create_check_attribute() -> Attribute {
  105. parse_quote! {
  106. #[doc = "CHECK: This program can modify the data of the component"]
  107. }
  108. }
  109. /// Generates the initialize function and struct.
  110. fn generate_initialize(component_type: &Type) -> (TokenStream2, TokenStream2) {
  111. (
  112. quote! {
  113. #[automatically_derived]
  114. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  115. let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
  116. 0, &ctx.accounts.instruction_sysvar_account.to_account_info()
  117. ).map_err(|_| BoltError::InvalidCaller)?;
  118. if instruction.program_id != World::id() {
  119. return Err(BoltError::InvalidCaller.into());
  120. }
  121. ctx.accounts.data.set_inner(<#component_type>::default());
  122. ctx.accounts.data.bolt_metadata.authority = *ctx.accounts.authority.key;
  123. Ok(())
  124. }
  125. },
  126. quote! {
  127. #[automatically_derived]
  128. #[derive(Accounts)]
  129. pub struct Initialize<'info> {
  130. #[account(mut)]
  131. pub payer: Signer<'info>,
  132. #[account(init_if_needed, payer = payer, space = <#component_type>::size(), seeds = [<#component_type>::seed(), entity.key().as_ref()], bump)]
  133. pub data: Account<'info, #component_type>,
  134. #[account()]
  135. pub entity: Account<'info, Entity>,
  136. #[account()]
  137. pub authority: AccountInfo<'info>,
  138. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  139. pub instruction_sysvar_account: UncheckedAccount<'info>,
  140. pub system_program: Program<'info, System>,
  141. }
  142. },
  143. )
  144. }
  145. /// Generates the instructions and related structs to inject in the component.
  146. fn generate_update(
  147. component_type: &Type,
  148. ) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) {
  149. (
  150. quote! {
  151. #[automatically_derived]
  152. pub fn update(ctx: Context<Update>, data: Vec<u8>) -> Result<()> {
  153. require!(ctx.accounts.bolt_component.bolt_metadata.authority == World::id() || (ctx.accounts.bolt_component.bolt_metadata.authority == *ctx.accounts.authority.key && ctx.accounts.authority.is_signer), BoltError::InvalidAuthority);
  154. // Check if the instruction is called from the world program
  155. let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
  156. 0, &ctx.accounts.instruction_sysvar_account.to_account_info()
  157. ).map_err(|_| BoltError::InvalidCaller)?;
  158. require_eq!(instruction.program_id, World::id(), BoltError::InvalidCaller);
  159. ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
  160. Ok(())
  161. }
  162. },
  163. quote! {
  164. #[automatically_derived]
  165. pub fn update_with_session(ctx: Context<UpdateWithSession>, data: Vec<u8>) -> Result<()> {
  166. if ctx.accounts.bolt_component.bolt_metadata.authority == World::id() {
  167. require!(Clock::get()?.unix_timestamp < ctx.accounts.session_token.valid_until, bolt_lang::session_keys::SessionError::InvalidToken);
  168. } else {
  169. let validity_ctx = bolt_lang::session_keys::ValidityChecker {
  170. session_token: ctx.accounts.session_token.clone(),
  171. session_signer: ctx.accounts.authority.clone(),
  172. authority: ctx.accounts.bolt_component.bolt_metadata.authority.clone(),
  173. target_program: World::id(),
  174. };
  175. require!(ctx.accounts.session_token.validate(validity_ctx)?, bolt_lang::session_keys::SessionError::InvalidToken);
  176. require_eq!(ctx.accounts.bolt_component.bolt_metadata.authority, ctx.accounts.session_token.authority, bolt_lang::session_keys::SessionError::InvalidToken);
  177. }
  178. // Check if the instruction is called from the world program
  179. let instruction = anchor_lang::solana_program::sysvar::instructions::get_instruction_relative(
  180. 0, &ctx.accounts.instruction_sysvar_account.to_account_info()
  181. ).map_err(|_| BoltError::InvalidCaller)?;
  182. require_eq!(instruction.program_id, World::id(), BoltError::InvalidCaller);
  183. ctx.accounts.bolt_component.set_inner(<#component_type>::try_from_slice(&data)?);
  184. Ok(())
  185. }
  186. },
  187. quote! {
  188. #[automatically_derived]
  189. #[derive(Accounts)]
  190. pub struct Update<'info> {
  191. #[account(mut)]
  192. pub bolt_component: Account<'info, #component_type>,
  193. #[account()]
  194. pub authority: Signer<'info>,
  195. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  196. pub instruction_sysvar_account: UncheckedAccount<'info>
  197. }
  198. },
  199. quote! {
  200. #[automatically_derived]
  201. #[derive(Accounts)]
  202. pub struct UpdateWithSession<'info> {
  203. #[account(mut)]
  204. pub bolt_component: Account<'info, #component_type>,
  205. #[account()]
  206. pub authority: Signer<'info>,
  207. #[account(address = anchor_lang::solana_program::sysvar::instructions::id())]
  208. pub instruction_sysvar_account: UncheckedAccount<'info>,
  209. #[account(constraint = session_token.to_account_info().owner == &bolt_lang::session_keys::ID)]
  210. pub session_token: Account<'info, bolt_lang::session_keys::SessionToken>,
  211. }
  212. },
  213. )
  214. }
  215. /// Checks if the field is expecting a program.
  216. fn is_expecting_program(field: &Field) -> bool {
  217. field.ty.to_token_stream().to_string().contains("Program")
  218. }