use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
use super::builder::LightAccountsBuilder;
pub(crate) fn derive_light_accounts(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
let builder = LightAccountsBuilder::parse(input)?;
builder.validate()?;
if !builder.has_instruction_args() {
return builder.generate_noop_impls();
}
let pre_init = builder.generate_pre_init_all()?;
let pre_init_impl = builder.generate_pre_init_impl(pre_init)?;
let finalize_body = quote! { Ok(()) };
let finalize_impl = builder.generate_finalize_impl(finalize_body)?;
Ok(quote! {
#pre_init_impl
#finalize_impl
})
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
#[test]
fn test_token_account_with_init_generates_create_cpi() {
let input: DeriveInput = parse_quote! {
#[instruction(params: CreateVaultParams)]
pub struct CreateVault<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
#[light_account(init, token::seeds = [b"vault"], token::mint = my_mint, token::owner = fee_payer, token::owner_seeds = [b"auth"])]
pub vault: Account<'info, CToken>,
pub light_token_config: Account<'info, CompressibleConfig>,
pub light_token_rent_sponsor: Account<'info, RentSponsor>,
pub light_token_cpi_authority: AccountInfo<'info>,
}
};
let result = derive_light_accounts(&input);
assert!(result.is_ok(), "Token account derive should succeed");
let output = result.unwrap().to_string();
assert!(
output.contains("LightPreInit"),
"Should generate LightPreInit impl"
);
assert!(
output.contains("create_accounts"),
"Should generate create_accounts call"
);
assert!(
output.contains("TokenInitParam"),
"Should generate TokenInitParam for vault"
);
}
#[test]
fn test_ata_with_init_generates_create_cpi() {
let input: DeriveInput = parse_quote! {
#[instruction(params: CreateAtaParams)]
pub struct CreateAta<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
#[light_account(init, associated_token::authority = wallet, associated_token::mint = my_mint)]
pub user_ata: Account<'info, CToken>,
pub wallet: AccountInfo<'info>,
pub my_mint: AccountInfo<'info>,
pub light_token_config: Account<'info, CompressibleConfig>,
pub light_token_rent_sponsor: Account<'info, RentSponsor>,
}
};
let result = derive_light_accounts(&input);
assert!(result.is_ok(), "ATA derive should succeed");
let output = result.unwrap().to_string();
assert!(
output.contains("LightPreInit"),
"Should generate LightPreInit impl"
);
assert!(
output.contains("create_accounts"),
"Should generate create_accounts call"
);
assert!(
output.contains("AtaInitParam"),
"Should generate AtaInitParam for ATA"
);
}
#[test]
fn test_token_mark_only_succeeds_with_pda() {
let input: DeriveInput = parse_quote! {
#[instruction(params: UseVaultParams)]
pub struct UseVault<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
#[account(init, payer = fee_payer, space = 8 + 100, seeds = [b"record"], bump)]
#[light_account(init)]
pub record: Account<'info, MyRecord>,
#[light_account(token::seeds = [b"vault"], token::owner_seeds = [b"auth"])]
pub vault: Account<'info, CToken>,
pub compression_config: Account<'info, CompressionConfig>,
pub pda_rent_sponsor: Account<'info, RentSponsor>,
}
};
let result = derive_light_accounts(&input);
assert!(
result.is_ok(),
"Token mark-only with PDA should succeed, got error: {:?}",
result.err()
);
}
#[test]
fn test_mixed_token_and_ata_generates_both() {
let input: DeriveInput = parse_quote! {
#[instruction(params: CreateBothParams)]
pub struct CreateBoth<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
#[light_account(init, token::seeds = [b"vault"], token::mint = my_mint, token::owner = fee_payer, token::owner_seeds = [b"auth"])]
pub vault: Account<'info, CToken>,
#[light_account(init, associated_token::authority = wallet, associated_token::mint = my_mint)]
pub user_ata: Account<'info, CToken>,
pub wallet: AccountInfo<'info>,
pub my_mint: AccountInfo<'info>,
pub light_token_config: Account<'info, CompressibleConfig>,
pub light_token_rent_sponsor: Account<'info, RentSponsor>,
pub light_token_cpi_authority: AccountInfo<'info>,
}
};
let result = derive_light_accounts(&input);
assert!(result.is_ok(), "Mixed token+ATA derive should succeed");
let output = result.unwrap().to_string();
assert!(
output.contains("LightPreInit"),
"Should generate LightPreInit impl"
);
assert!(
output.contains("create_accounts"),
"Should generate create_accounts call"
);
assert!(
output.contains("TokenInitParam"),
"Should generate TokenInitParam for vault"
);
assert!(
output.contains("AtaInitParam"),
"Should generate AtaInitParam for ATA"
);
}
#[test]
fn test_no_instruction_args_generates_noop() {
let input: DeriveInput = parse_quote! {
pub struct NoInstruction<'info> {
#[account(mut)]
pub fee_payer: Signer<'info>,
}
};
let result = derive_light_accounts(&input);
assert!(result.is_ok(), "No instruction args should succeed");
let output = result.unwrap().to_string();
assert!(
output.contains("LightPreInit"),
"Should generate LightPreInit impl"
);
assert!(
output.contains("LightFinalize"),
"Should generate LightFinalize impl"
);
assert!(
output.contains("Ok (false)") || output.contains("Ok(false)"),
"Should return Ok(false) in pre_init"
);
}
}