use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use super::{mint::InfraRefs, parse::ParsedPdaField};
pub(super) struct PdaIdents {
pub idx: u8,
pub account_info: Ident,
pub account_key: Ident,
pub address_tree_pubkey: Ident,
pub account_data: Ident,
}
impl PdaIdents {
pub fn new(idx: usize) -> Self {
Self {
idx: idx as u8,
account_info: format_ident!("__account_info_{}", idx),
account_key: format_ident!("__account_key_{}", idx),
address_tree_pubkey: format_ident!("__address_tree_pubkey_{}", idx),
account_data: format_ident!("__account_data_{}", idx),
}
}
}
pub(super) struct PdaBlockBuilder<'a> {
field: &'a ParsedPdaField,
idents: PdaIdents,
}
impl<'a> PdaBlockBuilder<'a> {
pub fn new(field: &'a ParsedPdaField, idx: usize) -> Self {
Self {
field,
idents: PdaIdents::new(idx),
}
}
fn account_extraction(&self) -> TokenStream {
let field_name = &self.field.field_name;
let account_info = &self.idents.account_info;
let account_key = &self.idents.account_key;
quote! {
let #account_info = self.#field_name.to_account_info();
let #account_key = #account_info.key.to_bytes();
}
}
fn address_tree_extraction(&self) -> TokenStream {
let addr_tree_info = self
.field
.address_tree_info
.as_ref()
.expect("address_tree_info required for derive macro");
let address_tree_pubkey = &self.idents.address_tree_pubkey;
quote! {
let #address_tree_pubkey: [u8; 32] = {
let tree_info: &light_account::PackedAddressTreeInfo = &#addr_tree_info;
let __tree_account = cpi_accounts
.get_tree_account_info(tree_info.address_merkle_tree_pubkey_index as usize)?;
light_account::AccountInfoTrait::key(__tree_account)
};
}
}
fn account_data_init(&self) -> TokenStream {
let ident = &self.field.field_name;
let account_data = &self.idents.account_data;
if self.field.is_zero_copy {
let account_guard = format_ident!("{}_guard", ident);
quote! {
{
let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot;
let mut #account_guard = self.#ident.load_init()
.map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?;
let #account_data = &mut *#account_guard;
#account_data.compression_info =
light_account::CompressionInfo::new_from_config(
&compression_config_data,
current_slot,
);
}
}
} else if self.field.is_boxed {
quote! {
{
use light_account::LightAccount;
use anchor_lang::AnchorSerialize;
let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot;
let account_info = self.#ident.to_account_info();
{
let #account_data = &mut **self.#ident;
#account_data.set_decompressed(&compression_config_data, current_slot);
}
let mut data = account_info
.try_borrow_mut_data()
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?;
self.#ident.serialize(&mut &mut data[8..])
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?;
}
}
} else {
quote! {
{
use light_account::LightAccount;
use anchor_lang::AnchorSerialize;
let current_slot = anchor_lang::solana_program::sysvar::clock::Clock::get()
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?.slot;
let account_info = self.#ident.to_account_info();
{
let #account_data = &mut *self.#ident;
#account_data.set_decompressed(&compression_config_data, current_slot);
}
let mut data = account_info
.try_borrow_mut_data()
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?;
self.#ident.serialize(&mut &mut data[8..])
.map_err(|_| light_account::LightSdkTypesError::ConstraintViolation)?;
}
}
}
}
fn prepare_call(&self) -> TokenStream {
let addr_tree_info = self
.field
.address_tree_info
.as_ref()
.expect("address_tree_info required for derive macro");
let output_tree = self
.field
.output_tree
.as_ref()
.expect("output_tree required for derive macro");
let account_key = &self.idents.account_key;
let address_tree_pubkey = &self.idents.address_tree_pubkey;
let idx = self.idents.idx;
quote! {
{
let tree_info: &light_account::PackedAddressTreeInfo = &#addr_tree_info;
::light_account::prepare_compressed_account_on_init(
&#account_key,
&#address_tree_pubkey,
tree_info,
#output_tree,
#idx,
&crate::LIGHT_CPI_SIGNER.program_id,
&mut all_new_address_params,
&mut all_compressed_infos,
)?;
}
}
}
pub fn build(&self) -> TokenStream {
let account_extraction = self.account_extraction();
let address_tree_extraction = self.address_tree_extraction();
let account_data_init = self.account_data_init();
let prepare_call = self.prepare_call();
quote! {
#account_extraction
#address_tree_extraction
#account_data_init
#prepare_call
}
}
}
pub(super) fn generate_pda_compress_blocks(fields: &[ParsedPdaField]) -> Vec<TokenStream> {
fields
.iter()
.enumerate()
.map(|(idx, field)| PdaBlockBuilder::new(field, idx).build())
.collect()
}
pub(super) fn generate_rent_reimbursement_block(
fields: &[ParsedPdaField],
infra: &InfraRefs,
) -> TokenStream {
if fields.is_empty() {
return quote! {};
}
let fee_payer = &infra.fee_payer;
let rent_sponsor = &infra.pda_rent_sponsor;
let account_info_exprs: Vec<TokenStream> = fields
.iter()
.map(|field| {
let field_name = &field.field_name;
quote! { self.#field_name.to_account_info() }
})
.collect();
let count = fields.len();
quote! {
{
let __created_accounts: [solana_account_info::AccountInfo<'info>; #count] = [
#(#account_info_exprs),*
];
let __rent_sponsor_bump_byte = [compression_config_data.rent_sponsor_bump];
let __rent_sponsor_seeds: &[&[u8]] = &[
light_account::RENT_SPONSOR_SEED,
&__rent_sponsor_bump_byte,
];
::light_account::reimburse_rent(
&__created_accounts,
&self.#fee_payer.to_account_info(),
&self.#rent_sponsor.to_account_info(),
__rent_sponsor_seeds,
)?;
}
}
}