use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse_macro_input, AttributeArgs, ItemMod, NestedMeta, Type}; /// This macro attribute is used to inject instructions and struct needed to delegate BOLT component. /// /// Components can be delegate in order to be updated in an Ephemeral Rollup /// /// # Example /// ```ignore /// /// #[component(delegate)] /// pub struct Position { /// pub x: i64, /// pub y: i64, /// pub z: i64, /// } /// ``` #[proc_macro_attribute] pub fn delegate(args: TokenStream, input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::ItemMod); let args = parse_macro_input!(args as syn::AttributeArgs); let component_type = extract_type_name(&args).expect("Expected a component type in macro arguments"); let modified = modify_component_module(ast, &component_type); TokenStream::from(quote! { #modified }) } /// Modifies the component module and adds the necessary functions and structs. fn modify_component_module(mut module: ItemMod, component_type: &Type) -> ItemMod { let (delegate_fn, delegate_struct) = generate_delegate(component_type); let (undelegate_fn, undelegate_struct) = generate_undelegate(); let (allow_undelegate_fn, allow_undelegate_struct) = generate_allow_undelegate(); module.content = module.content.map(|(brace, mut items)| { items.extend( vec![ delegate_fn, delegate_struct, undelegate_fn, undelegate_struct, allow_undelegate_fn, allow_undelegate_struct, ] .into_iter() .map(|item| syn::parse2(item).unwrap()) .collect::>(), ); (brace, items) }); module } /// Generates the allow_undelegate function and struct. fn generate_allow_undelegate() -> (TokenStream2, TokenStream2) { ( quote! { #[automatically_derived] pub fn allow_undelegation(ctx: Context) -> Result<()> { ::bolt_lang::allow_undelegation( &ctx.accounts.base_account, &ctx.accounts.delegation_record, &ctx.accounts.delegation_metadata, &ctx.accounts.buffer, &ctx.accounts.delegation_program, &id(), )?; Ok(()) } }, quote! { #[automatically_derived] #[derive(Accounts)] pub struct AllowUndelegation<'info> { #[account()] /// CHECK: The delegated component pub base_account: AccountInfo<'info>, #[account()] /// CHECK: delegation record pub delegation_record: AccountInfo<'info>, #[account(mut)] /// CHECK: delegation metadata pub delegation_metadata: AccountInfo<'info>, #[account()] /// CHECK: singer buffer to enforce CPI pub buffer: AccountInfo<'info>, #[account()] /// CHECK:` pub delegation_program: AccountInfo<'info>, } }, ) } /// Generates the undelegate function and struct. fn generate_undelegate() -> (TokenStream2, TokenStream2) { ( quote! { #[automatically_derived] pub fn process_undelegation(ctx: Context, account_seeds: Vec>) -> Result<()> { let [delegated_account, buffer, payer, system_program] = [ &ctx.accounts.base_account, &ctx.accounts.buffer, &ctx.accounts.payer, &ctx.accounts.system_program, ]; ::bolt_lang::undelegate_account( delegated_account, &id(), buffer, payer, system_program, account_seeds, )?; Ok(()) } }, quote! { #[automatically_derived] #[derive(Accounts)] pub struct InitializeAfterUndelegation<'info> { /// CHECK:` #[account(mut)] pub base_account: AccountInfo<'info>, /// CHECK:` #[account()] pub buffer: AccountInfo<'info>, /// CHECK: #[account(mut)] pub payer: AccountInfo<'info>, /// CHECK: pub system_program: AccountInfo<'info>, } }, ) } /// Generates the delegate instruction and related structs to inject in the component. fn generate_delegate(component_type: &Type) -> (TokenStream2, TokenStream2) { ( quote! { #[automatically_derived] pub fn delegate(ctx: Context, valid_until: i64, commit_frequency_ms: u32) -> Result<()> { let [payer, entity, account, owner_program, buffer, delegation_record, delegate_account_seeds, delegation_program, system_program] = [ &ctx.accounts.payer, &ctx.accounts.entity.to_account_info(), &ctx.accounts.account, &ctx.accounts.owner_program, &ctx.accounts.buffer, &ctx.accounts.delegation_record, &ctx.accounts.delegate_account_seeds, &ctx.accounts.delegation_program, &ctx.accounts.system_program, ]; let pda_seeds: &[&[u8]] = &[<#component_type>::seed(), &entity.key.to_bytes()]; ::bolt_lang::delegate_account( payer, account, owner_program, buffer, delegation_record, delegate_account_seeds, delegation_program, system_program, pda_seeds, valid_until, commit_frequency_ms, )?; Ok(()) } }, quote! { #[automatically_derived] #[derive(Accounts)] pub struct DelegateInput<'info> { pub payer: Signer<'info>, #[account()] pub entity: Account<'info, Entity>, /// CHECK: #[account(mut)] pub account: AccountInfo<'info>, /// CHECK:` pub owner_program: AccountInfo<'info>, /// CHECK: #[account(mut)] pub buffer: AccountInfo<'info>, /// CHECK:` #[account(mut)] pub delegation_record: AccountInfo<'info>, /// CHECK:` #[account(mut)] pub delegate_account_seeds: AccountInfo<'info>, /// CHECK:` pub delegation_program: AccountInfo<'info>, /// CHECK:` pub system_program: AccountInfo<'info>, } }, ) } /// Extracts the type name from attribute arguments. fn extract_type_name(args: &AttributeArgs) -> Option { args.iter().find_map(|arg| { if let NestedMeta::Meta(syn::Meta::Path(path)) = arg { Some(Type::Path(syn::TypePath { qself: None, path: path.clone(), })) } else { None } }) }