bolt_helpers_world_apply/
lib.rs

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