ephemeral_rollups_sdk_attribute_delegate/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned;
4use syn::{parse_macro_input, ItemStruct};
5
6#[proc_macro_attribute]
7pub fn delegate(_attr: TokenStream, item: TokenStream) -> TokenStream {
8    let input = parse_macro_input!(item as ItemStruct);
9
10    // Extract the struct name and fields
11    let struct_name = &input.ident;
12    let fields = &input.fields;
13    let original_attrs = &input.attrs;
14
15    // Process fields to modify them according to the rules
16    let mut new_fields = Vec::new();
17    let mut delegate_methods = Vec::new();
18    let mut has_owner_program = false;
19    let mut has_delegation_program = false;
20    let mut has_system_program = false;
21
22    for field in fields.iter() {
23        let mut field_attrs = field.attrs.clone();
24
25        let field_name = match &field.ident {
26            Some(name) => name,
27            None => {
28                return syn::Error::new_spanned(
29                    field,
30                    "Unnamed fields are not supported in this macro",
31                )
32                .to_compile_error()
33                .into();
34            }
35        };
36
37        // Check if the field has the `del` attribute
38        let has_del = field_attrs
39            .iter()
40            .any(|attr| attr.path.is_ident("account") && attr.tokens.to_string().contains("del"));
41
42        if has_del {
43            let buffer_field = syn::Ident::new(&format!("buffer_{field_name}"), field.span());
44            let delegation_record_field =
45                syn::Ident::new(&format!("delegation_record_{field_name}"), field.span());
46            let delegation_metadata_field =
47                syn::Ident::new(&format!("delegation_metadata_{field_name}"), field.span());
48
49            // Remove `del` from attributes
50            for attr in &mut field_attrs {
51                if attr.path.is_ident("account") {
52                    let tokens = attr.tokens.to_string();
53                    if tokens.contains("del") {
54                        let new_tokens = tokens
55                            .replace(", del", "")
56                            .replace("del, ", "")
57                            .replace("del", "");
58                        attr.tokens = syn::parse_str(&new_tokens).unwrap();
59                    }
60                }
61            }
62
63            // Add new fields
64            new_fields.push(quote! {
65                /// CHECK: The buffer account
66                #[account(
67                    mut, seeds = [ephemeral_rollups_sdk::consts::BUFFER, #field_name.key().as_ref()],
68                    bump, seeds::program = crate::id()
69                )]
70                pub #buffer_field: AccountInfo<'info>,
71            });
72
73            new_fields.push(quote! {
74                /// CHECK: The delegation record account
75                #[account(
76                    mut, seeds = [ephemeral_rollups_sdk::consts::DELEGATION_RECORD, #field_name.key().as_ref()],
77                    bump, seeds::program = delegation_program.key()
78                )]
79                pub #delegation_record_field: AccountInfo<'info>,
80            });
81
82            new_fields.push(quote! {
83                /// CHECK: The delegation metadata account
84                #[account(
85                    mut, seeds = [ephemeral_rollups_sdk::consts::DELEGATION_METADATA, #field_name.key().as_ref()],
86                    bump, seeds::program = delegation_program.key()
87                )]
88                pub #delegation_metadata_field: AccountInfo<'info>,
89            });
90
91            // Add delegate method
92            let delegate_method_name =
93                syn::Ident::new(&format!("delegate_{field_name}"), field.span());
94            delegate_methods.push(quote! {
95                pub fn #delegate_method_name<'a>(
96                    &'a self,
97                    payer: &'a Signer<'info>,
98                    seeds: &[&[u8]],
99                    config: ::ephemeral_rollups_sdk::cpi::DelegateConfig,
100                ) -> anchor_lang::solana_program::entrypoint::ProgramResult {
101                    let del_accounts = ::ephemeral_rollups_sdk::cpi::DelegateAccounts {
102                        payer,
103                        pda: &self.#field_name.to_account_info(),
104                        owner_program: &self.owner_program,
105                        buffer: &self.#buffer_field,
106                        delegation_record: &self.#delegation_record_field,
107                        delegation_metadata: &self.#delegation_metadata_field,
108                        delegation_program: &self.delegation_program,
109                        system_program: &self.system_program,
110                    };
111                    ::ephemeral_rollups_sdk::cpi::delegate_account(del_accounts, seeds, config)
112                }
113            });
114        }
115
116        // Add the original field without `del`
117        let field_type = &field.ty;
118        new_fields.push(quote! {
119            #(#field_attrs)*
120            pub #field_name: #field_type,
121        });
122
123        // Check for existing required fields
124        if field_name.eq("owner_program") {
125            has_owner_program = true;
126        }
127        if field_name.eq("delegation_program") {
128            has_delegation_program = true;
129        }
130        if field_name.eq("system_program") {
131            has_system_program = true;
132        }
133    }
134
135    // Add missing required fields
136    if !has_owner_program {
137        new_fields.push(quote! {
138            /// CHECK: The owner program of the pda
139            #[account(address = crate::id())]
140            pub owner_program: AccountInfo<'info>,
141        });
142    }
143    if !has_delegation_program {
144        new_fields.push(quote! {
145            /// CHECK: The delegation program
146            #[account(address = ::ephemeral_rollups_sdk::id())]
147            pub delegation_program: AccountInfo<'info>,
148        });
149    }
150    if !has_system_program {
151        new_fields.push(quote! {
152            pub system_program: Program<'info, System>,
153        });
154    }
155
156    // Generate the new struct definition
157    let expanded = quote! {
158        #(#original_attrs)*
159        pub struct #struct_name<'info> {
160            #(#new_fields)*
161        }
162
163        impl<'info> #struct_name<'info> {
164            #(#delegate_methods)*
165        }
166    };
167
168    TokenStream::from(expanded)
169}