diff --git a/sdk-libs/account-pinocchio/src/lib.rs b/sdk-libs/account-pinocchio/src/lib.rs index b68b611e51..326f3a1884 100644 --- a/sdk-libs/account-pinocchio/src/lib.rs +++ b/sdk-libs/account-pinocchio/src/lib.rs @@ -149,6 +149,11 @@ pub use light_sdk_types::interface::account::pack::Pack; pub use light_sdk_types::interface::account::token_seeds::{ PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, }; +// create_accounts SDK function and parameter types +#[cfg(feature = "token")] +pub use light_sdk_types::interface::accounts::create_accounts::{ + create_accounts, AtaInitParam, CreateMintsInput, PdaInitParam, SharedAccounts, TokenInitParam, +}; // Mint creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_mints::{ diff --git a/sdk-libs/account/src/lib.rs b/sdk-libs/account/src/lib.rs index ebbdcf5bdb..65e7e775a1 100644 --- a/sdk-libs/account/src/lib.rs +++ b/sdk-libs/account/src/lib.rs @@ -223,6 +223,11 @@ pub use light_sdk_types::interface::account::pack::Pack; pub use light_sdk_types::interface::account::token_seeds::{ PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds, }; +// create_accounts SDK function and parameter types +#[cfg(feature = "token")] +pub use light_sdk_types::interface::accounts::create_accounts::{ + create_accounts, AtaInitParam, CreateMintsInput, PdaInitParam, SharedAccounts, TokenInitParam, +}; // Mint creation CPI types and functions #[cfg(feature = "token")] pub use light_sdk_types::interface::cpi::create_mints::{ diff --git a/sdk-libs/macros/src/light_pdas/accounts/builder.rs b/sdk-libs/macros/src/light_pdas/accounts/builder.rs index 0e68f22e01..5beffafe46 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/builder.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/builder.rs @@ -4,14 +4,14 @@ //! providing methods for validation, querying, and code generation. use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::DeriveInput; use super::{ - mint::{InfraRefs, LightMintsBuilder}, - parse::ParsedLightAccountsStruct, + light_account::{AtaField, TokenAccountField}, + mint::{InfraRefs, LightMintField}, + parse::{ParsedLightAccountsStruct, ParsedPdaField}, pda::{generate_pda_compress_blocks, generate_rent_reimbursement_block}, - token::TokenAccountsBuilder, validation::{validate_struct, ValidationContext}, }; @@ -168,176 +168,16 @@ impl LightAccountsBuilder { }) } - /// Generate LightPreInit body for PDAs + mints: - /// 1. Write PDAs to CPI context - /// 2. Invoke CreateMintsCpi with CPI context offset - /// After this, Mints are "hot" and usable in instruction body - pub fn generate_pre_init_pdas_and_mints(&self) -> Result { - let body = self.generate_pre_init_pdas_and_mints_body()?; - Ok(quote! { - #body - Ok(true) - }) - } - - /// Generate LightPreInit body for mints-only (no PDAs): - /// Invoke CreateMintsCpi with decompress directly - /// After this, Mints are "hot" and usable in instruction body - pub fn generate_pre_init_mints_only(&self) -> Result { - let body = self.generate_pre_init_mints_only_body()?; - Ok(quote! { - #body - Ok(true) - }) - } - - /// Generate LightPreInit body for PDAs only (no mints) - /// After this, compressed addresses are registered - pub fn generate_pre_init_pdas_only(&self) -> Result { - let body = self.generate_pre_init_pdas_only_body()?; - Ok(quote! { - #body - Ok(true) - }) - } - - /// Generate unified pre_init body for ALL account types. - /// - /// This method handles all combinations of: - /// - PDAs (compressed accounts) - /// - Mints (compressed mints) - /// - Token accounts (vaults) - /// - ATAs (associated token accounts) + /// Generate LightPreInit body for PDAs only (no mints/tokens/ATAs). /// - /// ALL account creation happens here so accounts are available during - /// the instruction handler for transfers, minting, etc. - pub fn generate_pre_init_all(&self) -> Result { - let has_pdas = self.has_pdas(); - let has_mints = self.has_mints(); - - // Generate token/ATA creation code (if any) - let token_creation = TokenAccountsBuilder::new( - &self.parsed.token_fields, - &self.parsed.ata_fields, - &self.infra, - ) - .generate_pre_init_token_creation(); - - // Handle different combinations - match (has_pdas, has_mints, token_creation.is_some()) { - // PDAs + Mints + Tokens/ATAs - (true, true, true) => { - let pda_mint_body = self.generate_pre_init_pdas_and_mints_body()?; - let token_body = token_creation.unwrap(); - Ok(quote! { - #pda_mint_body - #token_body - Ok(true) - }) - } - // PDAs + Mints (no tokens) - (true, true, false) => self.generate_pre_init_pdas_and_mints(), - // PDAs + Tokens/ATAs (no mints) - (true, false, true) => { - let pda_body = self.generate_pre_init_pdas_only_body()?; - let token_body = token_creation.unwrap(); - Ok(quote! { - #pda_body - #token_body - Ok(true) - }) - } - // PDAs only - (true, false, false) => self.generate_pre_init_pdas_only(), - // Mints + Tokens/ATAs (no PDAs) - (false, true, true) => { - let mint_body = self.generate_pre_init_mints_only_body()?; - let token_body = token_creation.unwrap(); - Ok(quote! { - #mint_body - #token_body - Ok(true) - }) - } - // Mints only - (false, true, false) => self.generate_pre_init_mints_only(), - // Tokens/ATAs only (no PDAs, no mints) - (false, false, true) => { - let token_body = token_creation.unwrap(); - Ok(quote! { - #token_body - Ok(true) - }) - } - // Nothing to do - (false, false, false) => Ok(quote! { Ok(false) }), - } - } - - /// Generate PDAs + mints body WITHOUT the Ok(true) return. - fn generate_pre_init_pdas_and_mints_body(&self) -> Result { - let compress_blocks = generate_pda_compress_blocks(&self.parsed.pda_fields); - let rent_reimbursement = - generate_rent_reimbursement_block(&self.parsed.pda_fields, &self.infra); - let pda_count = self.parsed.pda_fields.len(); - let rentfree_count = pda_count as u8; - - // Get proof access expression (direct arg or nested in params) - let proof_access = self.get_proof_access()?; - - let first_pda_output_tree = self.parsed.pda_fields[0] - .output_tree - .as_ref() - .expect("output_tree required for derive macro"); - - let mints = &self.parsed.mint_fields; - let mint_invocation = LightMintsBuilder::new(mints, &proof_access, &self.infra) - .with_pda_context(pda_count, quote! { #first_pda_output_tree }) - .generate_invocation(); - - let fee_payer = &self.infra.fee_payer; - let compression_config = &self.infra.compression_config; - - Ok(quote! { - let cpi_accounts = light_account::CpiAccounts::new_with_config( - &self.#fee_payer, - _remaining, - light_account::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), - ); - let compression_config_data = light_account::LightConfig::load_checked( - &self.#compression_config, - &crate::LIGHT_CPI_SIGNER.program_id, - )?; - - let mut all_new_address_params = Vec::with_capacity(#rentfree_count as usize); - let mut all_compressed_infos = Vec::with_capacity(#rentfree_count as usize); - #(#compress_blocks)* - - // Reimburse fee payer for rent paid during PDA creation - #rent_reimbursement - - light_account::invoke_write_pdas_to_cpi_context( - crate::LIGHT_CPI_SIGNER, - #proof_access.proof.clone(), - &all_new_address_params, - &all_compressed_infos, - &cpi_accounts, - )?; - - #mint_invocation - }) - } - - /// Generate PDAs-only body WITHOUT the Ok(true) return. - fn generate_pre_init_pdas_only_body(&self) -> Result { + /// This path does NOT require the `token` feature on `light-account`. + fn generate_pre_init_pdas_only(&self) -> Result { let compress_blocks = generate_pda_compress_blocks(&self.parsed.pda_fields); let rent_reimbursement = generate_rent_reimbursement_block(&self.parsed.pda_fields, &self.infra); let rentfree_count = self.parsed.pda_fields.len() as u8; - // Get proof access expression (direct arg or nested in params) let proof_access = self.get_proof_access()?; - let fee_payer = &self.infra.fee_payer; let compression_config = &self.infra.compression_config; @@ -377,28 +217,211 @@ impl LightAccountsBuilder { read_only_accounts: vec![], }; instruction_data.invoke(cpi_accounts)?; + + Ok(true) }) } - /// Generate mints-only body WITHOUT the Ok(true) return. - fn generate_pre_init_mints_only_body(&self) -> Result { - // Get proof access expression (direct arg or nested in params) + /// Generate unified pre_init body for ALL account types. + /// + /// Uses `create_accounts()` for any combination involving mints/tokens/ATAs. + /// Falls back to direct PDA codegen for PDA-only (no `token` feature needed). + pub fn generate_pre_init_all(&self) -> Result { + let has_pdas = self.has_pdas(); + let has_mints = self.has_mints(); + let has_tokens_with_init = self.has_token_accounts_with_init(); + let has_atas_with_init = self.has_atas_with_init(); + let has_token_anything = has_mints || has_tokens_with_init || has_atas_with_init; + + match (has_pdas, has_token_anything) { + (false, false) => Ok(quote! { Ok(false) }), + (true, false) => self.generate_pre_init_pdas_only(), + (_, true) => self.generate_pre_init_with_create_accounts(), + } + } + + /// Generate pre_init body using `create_accounts()` SDK function. + /// + /// Handles all combinations involving mints, tokens, or ATAs (with or without PDAs). + /// Requires the `token` feature on `light-account`. + fn generate_pre_init_with_create_accounts(&self) -> Result { let proof_access = self.get_proof_access()?; + let has_pdas = self.has_pdas(); + let has_mints = self.has_mints(); + let has_tokens_with_init = self.has_token_accounts_with_init(); + let has_atas_with_init = self.has_atas_with_init(); + + let pda_count = self.parsed.pda_fields.len(); + let mint_count = self.parsed.mint_fields.len(); + let token_init_fields: Vec<&TokenAccountField> = self + .parsed + .token_fields + .iter() + .filter(|f| f.has_init) + .collect(); + let ata_init_fields: Vec<&AtaField> = self + .parsed + .ata_fields + .iter() + .filter(|f| f.has_init) + .collect(); + let token_init_count = token_init_fields.len(); + let ata_init_count = ata_init_fields.len(); + + // --- PDA account info bindings and PdaInitParam elements --- + let pda_info_bindings: Vec = self + .parsed + .pda_fields + .iter() + .enumerate() + .map(|(i, field)| { + let field_name = &field.field_name; + let info_ident = format_ident!("__pda_info_{}", i); + quote! { let #info_ident = self.#field_name.to_account_info(); } + }) + .collect(); + + let pda_init_params: Vec = (0..pda_count) + .map(|i| { + let info_ident = format_ident!("__pda_info_{}", i); + quote! { light_account::PdaInitParam { account: &#info_ident } } + }) + .collect(); - let mints = &self.parsed.mint_fields; - let mint_invocation = - LightMintsBuilder::new(mints, &proof_access, &self.infra).generate_invocation(); + // --- PDA setup closure body --- + let pda_setup_body = generate_pda_setup_closure_body(&self.parsed.pda_fields); + + // --- Mint params and CreateMintsInput --- + let (mint_bindings, mint_input_expr) = if has_mints { + generate_mint_input(&self.parsed.mint_fields)? + } else { + (quote! {}, quote! { None }) + }; + // --- Token init params --- + let (token_bindings, token_init_params) = + generate_token_init_params(&token_init_fields, &self.infra); + + // --- ATA init params --- + let (ata_bindings, ata_init_params) = generate_ata_init_params(&ata_init_fields); + + // --- Infrastructure account info bindings --- let fee_payer = &self.infra.fee_payer; + let mut infra_bindings = + vec![quote! { let __fee_payer_info = self.#fee_payer.to_account_info(); }]; + + let compression_config_opt = if has_pdas { + let cc = &self.infra.compression_config; + infra_bindings + .push(quote! { let __compression_config_info = self.#cc.to_account_info(); }); + quote! { Some(&__compression_config_info) } + } else { + quote! { None } + }; + + let has_token_infra = has_mints || has_tokens_with_init || has_atas_with_init; + let compressible_config_opt = if has_token_infra { + let ltc = &self.infra.light_token_config; + infra_bindings + .push(quote! { let __compressible_config_info = self.#ltc.to_account_info(); }); + quote! { Some(&__compressible_config_info) } + } else { + quote! { None } + }; + + let rent_sponsor_opt = if has_token_infra { + let ltrs = &self.infra.light_token_rent_sponsor; + infra_bindings.push(quote! { let __rent_sponsor_info = self.#ltrs.to_account_info(); }); + quote! { Some(&__rent_sponsor_info) } + } else { + quote! { None } + }; + + let cpi_authority_opt = if has_mints { + let ltca = &self.infra.light_token_cpi_authority; + infra_bindings + .push(quote! { let __cpi_authority_info = self.#ltca.to_account_info(); }); + quote! { Some(&__cpi_authority_info) } + } else { + quote! { None } + }; + + let system_program_opt = if has_tokens_with_init || has_atas_with_init { + infra_bindings.push( + quote! { let __system_program_info = self.system_program.to_account_info(); }, + ); + quote! { Some(&__system_program_info) } + } else { + quote! { None } + }; + + // --- Reimburse rent for PDAs (Anchor-specific) --- + let reimburse_block = if has_pdas { + let compression_config = &self.infra.compression_config; + let rent_reimbursement = + generate_rent_reimbursement_block(&self.parsed.pda_fields, &self.infra); + quote! { + let compression_config_data = light_account::LightConfig::load_checked( + &self.#compression_config, + &crate::LIGHT_CPI_SIGNER.program_id, + )?; + #rent_reimbursement + } + } else { + quote! {} + }; Ok(quote! { - let cpi_accounts = light_account::CpiAccounts::new_with_config( - &self.#fee_payer, + // Infrastructure account info bindings + #(#infra_bindings)* + + // PDA account info bindings + #(#pda_info_bindings)* + + // Mint params + #mint_bindings + + // Token params + #token_bindings + + // ATA params + #ata_bindings + + // Create all accounts via SDK function + light_account::create_accounts::< + solana_account_info::AccountInfo<'info>, + #pda_count, + #mint_count, + #token_init_count, + #ata_init_count, + _, + >( + [#(#pda_init_params),*], + |__light_config, __current_slot| { + #pda_setup_body + Ok(()) + }, + #mint_input_expr, + [#(#token_init_params),*], + [#(#ata_init_params),*], + &light_account::SharedAccounts { + fee_payer: &__fee_payer_info, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: &#proof_access, + program_id: crate::LIGHT_CPI_SIGNER.program_id, + compression_config: #compression_config_opt, + compressible_config: #compressible_config_opt, + rent_sponsor: #rent_sponsor_opt, + cpi_authority: #cpi_authority_opt, + system_program: #system_program_opt, + }, _remaining, - light_account::CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER), - ); + )?; + + // Reimburse fee payer for rent paid during PDA creation (Anchor-specific) + #reimburse_block - #mint_invocation + Ok(true) }) } @@ -453,3 +476,416 @@ impl LightAccountsBuilder { }) } } + +// ============================================================================ +// Helper functions for create_accounts() code generation +// ============================================================================ + +/// Generate the pda_setup closure body for `create_accounts()`. +/// +/// For each PDA field, generates `set_decompressed` calls using the closure's +/// `__light_config` and `__current_slot` parameters. +fn generate_pda_setup_closure_body(fields: &[ParsedPdaField]) -> TokenStream { + if fields.is_empty() { + return quote! {}; + } + + let blocks: Vec = fields.iter().map(|field| { + let ident = &field.field_name; + + if field.is_zero_copy { + // AccountLoader: load_init() for zero-copy (Pod) accounts + quote! { + { + let mut __guard = self.#ident.load_init() + .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; + __guard.compression_info = + light_account::CompressionInfo::new_from_config( + __light_config, __current_slot, + ); + } + } + } else if field.is_boxed { + // Box>: deref twice to get inner data, then serialize + quote! { + { + use light_account::LightAccount; + use anchor_lang::AnchorSerialize; + // Get account info BEFORE mutable borrow + let account_info = self.#ident.to_account_info(); + { + (**self.#ident).set_decompressed(__light_config, __current_slot); + } + // Serialize to on-chain buffer so data is visible before Anchor exit + 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 { + // Account: deref once to get inner data, then serialize + quote! { + { + use light_account::LightAccount; + use anchor_lang::AnchorSerialize; + // Get account info BEFORE mutable borrow + let account_info = self.#ident.to_account_info(); + { + (*self.#ident).set_decompressed(__light_config, __current_slot); + } + // Serialize to on-chain buffer so data is visible before Anchor exit + 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)?; + } + } + } + }).collect(); + + quote! { #(#blocks)* } +} + +/// Generate mint params bindings and `CreateMintsInput` expression. +/// +/// Returns (bindings_code, input_expr) where: +/// - `bindings_code` is the code to build `SingleMintParams` for each mint +/// - `input_expr` is `Some(light_account::CreateMintsInput { ... })` +fn generate_mint_input(mints: &[LightMintField]) -> Result<(TokenStream, TokenStream), syn::Error> { + let mint_count = mints.len(); + + // Per-mint param building code (adapted from LightMintsBuilder::generate_invocation) + let mint_params_builds: Vec = mints.iter().enumerate().map(|(idx, mint)| { + let mint_signer = &mint.mint_signer; + let authority = &mint.authority; + let decimals = &mint.decimals; + let freeze_authority = mint.freeze_authority.as_ref() + .map(|f| quote! { Some(self.#f.to_account_info().key.to_bytes()) }) + .unwrap_or_else(|| quote! { None }); + let mint_seeds = &mint.mint_seeds; + + let idx_ident = format_ident!("__mint_param_{}", idx); + let signer_key_ident = format_ident!("__mint_signer_key_{}", idx); + let mint_seeds_ident = format_ident!("__mint_seeds_{}", idx); + let mint_seeds_with_bump_ident = format_ident!("__mint_seeds_with_bump_{}", idx); + let mint_signer_bump_ident = format_ident!("__mint_signer_bump_{}", idx); + let mint_bump_slice_ident = format_ident!("__mint_bump_slice_{}", idx); + let auth_bump_slice_ident = format_ident!("__auth_bump_slice_{}", idx); + let authority_seeds_ident = format_ident!("__authority_seeds_{}", idx); + let authority_seeds_with_bump_ident = format_ident!("__authority_seeds_with_bump_{}", idx); + let authority_bump_ident = format_ident!("__authority_bump_{}", idx); + let token_metadata_ident = format_ident!("__mint_token_metadata_{}", idx); + + // Mint bump derivation + let mint_bump_derivation = mint.mint_bump.as_ref() + .map(|b| quote! { let #mint_signer_bump_ident: u8 = #b; }) + .unwrap_or_else(|| { + quote! { + let #mint_signer_bump_ident: u8 = { + let (_, bump) = solana_pubkey::Pubkey::find_program_address( + #mint_seeds_ident, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + bump + }; + } + }); + + // Authority seeds binding + let authority_seeds_binding = match &mint.authority_seeds { + Some(seeds) => { + let authority_bump_derivation = mint.authority_bump.as_ref() + .map(|b| quote! { let #authority_bump_ident: u8 = #b; }) + .unwrap_or_else(|| { + quote! { + let #authority_bump_ident: u8 = { + let base_seeds: &[&[u8]] = #seeds; + let (_, bump) = solana_pubkey::Pubkey::find_program_address( + base_seeds, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + bump + }; + } + }); + quote! { + let #authority_seeds_ident: &[&[u8]] = #seeds; + #authority_bump_derivation + let mut #authority_seeds_with_bump_ident: Vec<&[u8]> = #authority_seeds_ident.to_vec(); + let #auth_bump_slice_ident: &[u8] = &[#authority_bump_ident]; + #authority_seeds_with_bump_ident.push(#auth_bump_slice_ident); + let #authority_seeds_with_bump_ident: Option> = Some(#authority_seeds_with_bump_ident); + } + } + None => quote! { + let #authority_seeds_with_bump_ident: Option> = None; + }, + }; + + // Token metadata binding + let has_metadata = mint.name.is_some(); + let token_metadata_binding = if has_metadata { + let name_expr = mint.name.as_ref().map(|e| quote! { #e }).unwrap(); + let symbol_expr = mint.symbol.as_ref().map(|e| quote! { #e }).unwrap(); + let uri_expr = mint.uri.as_ref().map(|e| quote! { #e }).unwrap(); + let update_authority_expr = mint.update_authority.as_ref() + .map(|f| quote! { Some(self.#f.to_account_info().key.to_bytes().into()) }) + .unwrap_or_else(|| quote! { None }); + let additional_metadata_expr = mint.additional_metadata.as_ref() + .map(|e| quote! { #e }) + .unwrap_or_else(|| quote! { None }); + + quote! { + let #token_metadata_ident: Option = Some( + light_account::TokenMetadataInstructionData { + update_authority: #update_authority_expr, + name: #name_expr, + symbol: #symbol_expr, + uri: #uri_expr, + additional_metadata: #additional_metadata_expr, + } + ); + } + } else { + quote! { + let #token_metadata_ident: Option = None; + } + }; + + quote! { + let #signer_key_ident: [u8; 32] = self.#mint_signer.to_account_info().key.to_bytes(); + + let #mint_seeds_ident: &[&[u8]] = #mint_seeds; + #mint_bump_derivation + let mut #mint_seeds_with_bump_ident: Vec<&[u8]> = #mint_seeds_ident.to_vec(); + let #mint_bump_slice_ident: &[u8] = &[#mint_signer_bump_ident]; + #mint_seeds_with_bump_ident.push(#mint_bump_slice_ident); + + #authority_seeds_binding + #token_metadata_binding + + let #idx_ident = light_account::SingleMintParams { + decimals: #decimals, + mint_authority: self.#authority.to_account_info().key.to_bytes(), + mint_bump: None, + freeze_authority: #freeze_authority, + mint_seed_pubkey: #signer_key_ident, + authority_seeds: #authority_seeds_with_bump_ident.as_deref(), + mint_signer_seeds: Some(&#mint_seeds_with_bump_ident[..]), + token_metadata: #token_metadata_ident.as_ref(), + }; + } + }).collect(); + + // Authority signer checks + let authority_signer_checks: Vec = mints.iter() + .filter(|m| m.authority_seeds.is_none()) + .map(|mint| { + let authority = &mint.authority; + quote! { + if !self.#authority.to_account_info().is_signer { + return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature.into()); + } + } + }).collect(); + + // Array element identifiers + let param_idents: Vec = (0..mint_count) + .map(|idx| { + let ident = format_ident!("__mint_param_{}", idx); + quote! { #ident } + }) + .collect(); + + let mint_seed_account_exprs: Vec = mints + .iter() + .map(|mint| { + let mint_signer = &mint.mint_signer; + quote! { self.#mint_signer.to_account_info() } + }) + .collect(); + + let mint_account_exprs: Vec = mints + .iter() + .map(|mint| { + let field_ident = &mint.field_ident; + quote! { self.#field_ident.to_account_info() } + }) + .collect(); + + let bindings = quote! { + #(#mint_params_builds)* + #(#authority_signer_checks)* + }; + + let input_expr = quote! { + Some(light_account::CreateMintsInput { + params: [#(#param_idents),*], + mint_seed_accounts: [#(#mint_seed_account_exprs),*], + mint_accounts: [#(#mint_account_exprs),*], + }) + }; + + Ok((bindings, input_expr)) +} + +/// Generate token vault init param bindings and `TokenInitParam` array elements. +fn generate_token_init_params( + fields: &[&TokenAccountField], + infra: &InfraRefs, +) -> (TokenStream, Vec) { + if fields.is_empty() { + return (quote! {}, vec![]); + } + + let mut all_bindings = Vec::new(); + let mut params = Vec::new(); + + for (i, field) in fields.iter().enumerate() { + let field_ident = &field.field_ident; + let account_info_ident = format_ident!("__token_account_info_{}", i); + let mint_info_ident = format_ident!("__token_mint_info_{}", i); + + // Bind account info + all_bindings.push(quote! { + let #account_info_ident = self.#field_ident.to_account_info(); + }); + + // Bind mint info + let mint_binding = field + .mint + .as_ref() + .map(|m| quote! { let #mint_info_ident = self.#m.to_account_info(); }) + .unwrap_or_else(|| quote! { let #mint_info_ident = self.mint.to_account_info(); }); + all_bindings.push(mint_binding); + + // Owner expression + let owner_expr = field + .owner + .as_ref() + .map(|o| quote! { self.#o.to_account_info().key.to_bytes() }) + .unwrap_or_else(|| { + let fee_payer = &infra.fee_payer; + quote! { self.#fee_payer.to_account_info().key.to_bytes() } + }); + + // Seed bindings and bump derivation + let token_seeds = &field.seeds; + let seed_val_idents: Vec = (0..token_seeds.len()) + .map(|j| format_ident!("__tseed_{}_{}", i, j)) + .collect(); + let seed_ref_idents: Vec = (0..token_seeds.len()) + .map(|j| format_ident!("__tseed_ref_{}_{}", i, j)) + .collect(); + + for (j, seed) in token_seeds.iter().enumerate() { + let val_name = &seed_val_idents[j]; + let ref_name = &seed_ref_idents[j]; + all_bindings.push(quote! { + let #val_name = #seed; + let #ref_name: &[u8] = #val_name.as_ref(); + }); + } + + let bump_ident = format_ident!("__token_bump_{}", i); + let bump_derivation = field + .bump + .as_ref() + .map(|b| quote! { let #bump_ident: u8 = #b; }) + .unwrap_or_else(|| { + let seed_refs: Vec<&syn::Ident> = seed_ref_idents.iter().collect(); + if token_seeds.is_empty() { + quote! { + let #bump_ident: u8 = { + let (_, bump) = solana_pubkey::Pubkey::find_program_address( + &[], + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + bump + }; + } + } else { + quote! { + let #bump_ident: u8 = { + let seeds: &[&[u8]] = &[#(#seed_refs),*]; + let (_, bump) = solana_pubkey::Pubkey::find_program_address( + seeds, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + bump + }; + } + } + }); + all_bindings.push(bump_derivation); + + let bump_slice_ident = format_ident!("__token_bump_slice_{}", i); + let seeds_ident = format_ident!("__token_seeds_{}", i); + all_bindings.push(quote! { + let #bump_slice_ident: [u8; 1] = [#bump_ident]; + }); + + let seeds_array_expr = if token_seeds.is_empty() { + quote! { &[&#bump_slice_ident[..]] } + } else { + let seed_refs: Vec<&syn::Ident> = seed_ref_idents.iter().collect(); + quote! { &[#(#seed_refs,)* &#bump_slice_ident[..]] } + }; + all_bindings.push(quote! { + let #seeds_ident: &[&[u8]] = #seeds_array_expr; + }); + + params.push(quote! { + light_account::TokenInitParam { + account: &#account_info_ident, + mint: &#mint_info_ident, + owner: #owner_expr, + seeds: #seeds_ident, + } + }); + } + + let bindings = quote! { #(#all_bindings)* }; + (bindings, params) +} + +/// Generate ATA init param bindings and `AtaInitParam` array elements. +fn generate_ata_init_params(fields: &[&AtaField]) -> (TokenStream, Vec) { + if fields.is_empty() { + return (quote! {}, vec![]); + } + + let mut all_bindings = Vec::new(); + let mut params = Vec::new(); + + for (i, field) in fields.iter().enumerate() { + let field_ident = &field.field_ident; + let owner = &field.owner; + let mint = &field.mint; + + let ata_info_ident = format_ident!("__ata_info_{}", i); + let owner_info_ident = format_ident!("__ata_owner_info_{}", i); + let mint_info_ident = format_ident!("__ata_mint_info_{}", i); + + all_bindings.push(quote! { + let #ata_info_ident = self.#field_ident.to_account_info(); + let #owner_info_ident = self.#owner.to_account_info(); + let #mint_info_ident = self.#mint.to_account_info(); + }); + + params.push(quote! { + light_account::AtaInitParam { + ata: &#ata_info_ident, + owner: &#owner_info_ident, + mint: &#mint_info_ident, + idempotent: true, + } + }); + } + + let bindings = quote! { #(#all_bindings)* }; + (bindings, params) +} diff --git a/sdk-libs/macros/src/light_pdas/accounts/derive.rs b/sdk-libs/macros/src/light_pdas/accounts/derive.rs index 536dcaa722..fa94364e09 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/derive.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/derive.rs @@ -85,22 +85,18 @@ mod tests { let output = result.unwrap().to_string(); - // Verify pre_init generates token account creation + // Verify pre_init generates create_accounts call assert!( output.contains("LightPreInit"), "Should generate LightPreInit impl" ); assert!( - output.contains("CreateTokenAccountCpi"), - "Should generate CreateTokenAccountCpi call" + output.contains("create_accounts"), + "Should generate create_accounts call" ); assert!( - output.contains("rent_free"), - "Should call rent_free on CreateTokenAccountCpi" - ); - assert!( - output.contains("invoke_signed"), - "Should call invoke_signed with seeds" + output.contains("TokenInitParam"), + "Should generate TokenInitParam for vault" ); } @@ -128,14 +124,18 @@ mod tests { let output = result.unwrap().to_string(); - // Verify pre_init generates ATA creation + // Verify pre_init generates create_accounts call with ATA assert!( output.contains("LightPreInit"), "Should generate LightPreInit impl" ); assert!( - output.contains("CreateTokenAtaCpi"), - "Should generate CreateTokenAtaCpi call" + output.contains("create_accounts"), + "Should generate create_accounts call" + ); + assert!( + output.contains("AtaInitParam"), + "Should generate AtaInitParam for ATA" ); } @@ -202,18 +202,22 @@ mod tests { let output = result.unwrap().to_string(); - // Should have both creation types in pre_init + // Should have create_accounts call with both token and ATA params assert!( output.contains("LightPreInit"), "Should generate LightPreInit impl" ); assert!( - output.contains("CreateTokenAccountCpi"), - "Should generate CreateTokenAccountCpi for vault" + output.contains("create_accounts"), + "Should generate create_accounts call" + ); + assert!( + output.contains("TokenInitParam"), + "Should generate TokenInitParam for vault" ); assert!( - output.contains("CreateTokenAtaCpi"), - "Should generate CreateTokenAtaCpi for ATA" + output.contains("AtaInitParam"), + "Should generate AtaInitParam for ATA" ); } diff --git a/sdk-libs/macros/src/light_pdas/accounts/light_account.rs b/sdk-libs/macros/src/light_pdas/accounts/light_account.rs index bb6b13a990..f14dac80ab 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/light_account.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/light_account.rs @@ -608,7 +608,7 @@ pub(crate) fn parse_light_account_attr( )?)))) } LightAccountType::Mint => Ok(Some(LightAccountField::Mint(Box::new( - build_mint_field(field_ident, &args.key_values, attr, direct_proof_arg)?, + build_mint_field(field_ident, &args.key_values, attr)?, )))), LightAccountType::Token => Ok(Some(LightAccountField::TokenAccount(Box::new( build_token_account_field(field_ident, &args.key_values, args.has_init, attr)?, @@ -740,7 +740,6 @@ fn build_mint_field( field_ident: &Ident, key_values: &[NamespacedKeyValue], attr: &syn::Attribute, - direct_proof_arg: &Option, ) -> Result { // Required fields let mut mint_signer: Option = None; @@ -835,19 +834,11 @@ fn build_mint_field( attr, )?; - // Always fetch from CreateAccountsProof - depends on whether proof is direct arg or nested - let address_tree_info = if let Some(proof_ident) = direct_proof_arg { - syn::parse_quote!(#proof_ident.address_tree_info) - } else { - syn::parse_quote!(params.create_accounts_proof.address_tree_info) - }; - Ok(LightMintField { field_ident: field_ident.clone(), mint_signer, authority, decimals, - address_tree_info, freeze_authority, mint_seeds, mint_bump, @@ -1799,21 +1790,6 @@ mod tests { match result.unwrap() { LightAccountField::Mint(mint) => { assert_eq!(mint.field_ident.to_string(), "cmint"); - - // Verify default address_tree_info uses the direct proof identifier - // Should be: create_proof.address_tree_info - let addr_tree_info = &mint.address_tree_info; - let addr_tree_str = quote::quote!(#addr_tree_info).to_string(); - assert!( - addr_tree_str.contains("create_proof"), - "address_tree_info should reference 'create_proof', got: {}", - addr_tree_str - ); - assert!( - addr_tree_str.contains("address_tree_info"), - "address_tree_info should access .address_tree_info field, got: {}", - addr_tree_str - ); } _ => panic!("Expected Mint field"), } diff --git a/sdk-libs/macros/src/light_pdas/accounts/mint.rs b/sdk-libs/macros/src/light_pdas/accounts/mint.rs index ecd3f2e817..fd5088b4f6 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/mint.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/mint.rs @@ -32,8 +32,6 @@ pub(crate) struct LightMintField { pub authority: Expr, /// Decimals for the mint pub decimals: Expr, - /// Address tree info expression (auto-fetched from CreateAccountsProof) - pub address_tree_info: Expr, /// Optional freeze authority pub freeze_authority: Option, /// Signer seeds for the mint_signer PDA (required, WITHOUT bump - bump is auto-derived or provided via mint_bump) @@ -100,305 +98,3 @@ impl InfraRefs { } } } - -/// Builder for generating code that creates multiple compressed mints using CreateMintsCpi. -/// -/// This replaces the previous single-mint LightMintBuilder with support for N mints. -/// Generated code uses `CreateMintsCpi` from light_token for optimal batching. -/// -/// Usage: -/// ```ignore -/// LightMintsBuilder::new(mints, &proof_access, &infra) -/// .with_pda_context(pda_count, quote! { #first_pda_output_tree }) -/// .generate_invocation() -/// ``` -pub(super) struct LightMintsBuilder<'a> { - mints: &'a [LightMintField], - /// TokenStream for accessing CreateAccountsProof (e.g., `proof` or `params.create_accounts_proof`) - proof_access: &'a TokenStream, - infra: &'a InfraRefs, - /// PDA context: (pda_count, output_tree_expr) for batching with PDAs - pda_context: Option<(usize, TokenStream)>, -} - -impl<'a> LightMintsBuilder<'a> { - /// Create builder with required fields. - pub fn new( - mints: &'a [LightMintField], - proof_access: &'a TokenStream, - infra: &'a InfraRefs, - ) -> Self { - Self { - mints, - proof_access, - infra, - pda_context: None, - } - } - - /// Configure for batching with PDAs. - /// - /// When PDAs are written to CPI context first, this sets the offset for mint indices - /// so they don't collide with PDA indices. - pub fn with_pda_context(mut self, pda_count: usize, output_tree_expr: TokenStream) -> Self { - self.pda_context = Some((pda_count, output_tree_expr)); - self - } - - /// Generate CreateMintsCpi invocation code for all mints. - pub fn generate_invocation(self) -> TokenStream { - generate_mints_invocation(&self) - } -} - -/// Generate CreateMintsCpi invocation code for multiple mints. -/// -/// Flow: -/// 1. For each mint: derive PDA, build SingleMintParams -/// 2. Build arrays for mint_seed_accounts, mints -/// 3. Construct CreateMintsCpi struct -/// 4. Call invoke() - seeds are extracted from SingleMintParams internally -fn generate_mints_invocation(builder: &LightMintsBuilder) -> TokenStream { - let mints = builder.mints; - let proof_access = builder.proof_access; - let infra = builder.infra; - let mint_count = mints.len(); - - // Infrastructure field references - let fee_payer = &infra.fee_payer; - let light_token_config = &infra.light_token_config; - let light_token_rent_sponsor = &infra.light_token_rent_sponsor; - let light_token_cpi_authority = &infra.light_token_cpi_authority; - - // Determine CPI context offset based on PDA context - let (cpi_context_offset, output_tree_setup) = match &builder.pda_context { - Some((pda_count, tree_expr)) => { - let offset = *pda_count as u8; - ( - quote! { #offset }, - quote! { let __output_tree_index = #tree_expr; }, - ) - } - None => (quote! { 0u8 }, quote! {}), - }; - - // Generate code for each mint to build SingleMintParams - let mint_params_builds: Vec = mints - .iter() - .enumerate() - .map(|(idx, mint)| { - let mint_signer = &mint.mint_signer; - let authority = &mint.authority; - let decimals = &mint.decimals; - let address_tree_info = &mint.address_tree_info; - let freeze_authority = mint - .freeze_authority - .as_ref() - .map(|f| quote! { Some(self.#f.to_account_info().key.to_bytes()) }) - .unwrap_or_else(|| quote! { None }); - let mint_seeds = &mint.mint_seeds; - let authority_seeds = &mint.authority_seeds; - - let idx_ident = format_ident!("__mint_param_{}", idx); - let signer_key_ident = format_ident!("__mint_signer_key_{}", idx); - let mint_seeds_ident = format_ident!("__mint_seeds_{}", idx); - let mint_seeds_with_bump_ident = format_ident!("__mint_seeds_with_bump_{}", idx); - let mint_signer_bump_ident = format_ident!("__mint_signer_bump_{}", idx); - let authority_seeds_ident = format_ident!("__authority_seeds_{}", idx); - let authority_seeds_with_bump_ident = format_ident!("__authority_seeds_with_bump_{}", idx); - let authority_bump_ident = format_ident!("__authority_bump_{}", idx); - let token_metadata_ident = format_ident!("__mint_token_metadata_{}", idx); - - // Generate mint_seeds binding with bump derivation/appending - // User provides base seeds WITHOUT bump, we auto-derive or use provided bump - let mint_bump_derivation = mint.mint_bump - .as_ref() - .map(|b| quote! { let #mint_signer_bump_ident: u8 = #b; }) - .unwrap_or_else(|| { - // Auto-derive bump from mint_seeds - quote! { - let #mint_signer_bump_ident: u8 = { - let (_, bump) = solana_pubkey::Pubkey::find_program_address(#mint_seeds_ident, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); - bump - }; - } - }); - - // Generate optional authority seeds binding with bump derivation/appending - let authority_seeds_binding = match authority_seeds { - Some(seeds) => { - let authority_bump_derivation = mint.authority_bump - .as_ref() - .map(|b| quote! { let #authority_bump_ident: u8 = #b; }) - .unwrap_or_else(|| { - // Auto-derive bump from authority_seeds - quote! { - let #authority_bump_ident: u8 = { - let base_seeds: &[&[u8]] = #seeds; - let (_, bump) = solana_pubkey::Pubkey::find_program_address(base_seeds, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); - bump - }; - } - }); - quote! { - let #authority_seeds_ident: &[&[u8]] = #seeds; - #authority_bump_derivation - // Build Vec with bump appended (using Vec since we can't create fixed-size array at compile time) - let mut #authority_seeds_with_bump_ident: Vec<&[u8]> = #authority_seeds_ident.to_vec(); - let __auth_bump_slice: &[u8] = &[#authority_bump_ident]; - #authority_seeds_with_bump_ident.push(__auth_bump_slice); - let #authority_seeds_with_bump_ident: Option> = Some(#authority_seeds_with_bump_ident); - } - }, - None => quote! { - let #authority_seeds_with_bump_ident: Option> = None; - }, - }; - - // Check if metadata is present (validation guarantees name/symbol/uri are all-or-nothing) - let has_metadata = mint.name.is_some(); - - // Generate token_metadata binding - let token_metadata_binding = if has_metadata { - // name, symbol, uri are guaranteed to be present by validation - let name_expr = mint.name.as_ref().map(|e| quote! { #e }).unwrap(); - let symbol_expr = mint.symbol.as_ref().map(|e| quote! { #e }).unwrap(); - let uri_expr = mint.uri.as_ref().map(|e| quote! { #e }).unwrap(); - let update_authority_expr = mint.update_authority.as_ref() - .map(|f| quote! { Some(self.#f.to_account_info().key.to_bytes().into()) }) - .unwrap_or_else(|| quote! { None }); - let additional_metadata_expr = mint.additional_metadata.as_ref() - .map(|e| quote! { #e }) - .unwrap_or_else(|| quote! { None }); - - quote! { - let #token_metadata_ident: Option = Some( - light_account::TokenMetadataInstructionData { - update_authority: #update_authority_expr, - name: #name_expr, - symbol: #symbol_expr, - uri: #uri_expr, - additional_metadata: #additional_metadata_expr, - } - ); - } - } else { - quote! { - let #token_metadata_ident: Option = None; - } - }; - - quote! { - // Mint #idx: build params (mint and compression_address derived internally) - let #signer_key_ident: [u8; 32] = self.#mint_signer.to_account_info().key.to_bytes(); - - // Bind base mint_seeds (WITHOUT bump) and derive/get bump - let #mint_seeds_ident: &[&[u8]] = #mint_seeds; - #mint_bump_derivation - // Build Vec with bump appended - let mut #mint_seeds_with_bump_ident: Vec<&[u8]> = #mint_seeds_ident.to_vec(); - let __mint_bump_slice: &[u8] = &[#mint_signer_bump_ident]; - #mint_seeds_with_bump_ident.push(__mint_bump_slice); - - #authority_seeds_binding - #token_metadata_binding - - let __tree_info = &#address_tree_info; - - let #idx_ident = light_account::SingleMintParams { - decimals: #decimals, - mint_authority: self.#authority.to_account_info().key.to_bytes(), - mint_bump: None, // derived internally from mint_seed_pubkey - freeze_authority: #freeze_authority, - mint_seed_pubkey: #signer_key_ident, - authority_seeds: #authority_seeds_with_bump_ident.as_deref(), - mint_signer_seeds: Some(&#mint_seeds_with_bump_ident[..]), - token_metadata: #token_metadata_ident.as_ref(), - }; - } - }) - .collect(); - - // Generate array of SingleMintParams - let param_idents: Vec = (0..mint_count) - .map(|idx| { - let ident = format_ident!("__mint_param_{}", idx); - quote! { #ident } - }) - .collect(); - - // Generate array of mint seed AccountInfos - let mint_seed_account_exprs: Vec = mints - .iter() - .map(|mint| { - let mint_signer = &mint.mint_signer; - quote! { self.#mint_signer.to_account_info() } - }) - .collect(); - - // Generate array of mint AccountInfos - let mint_account_exprs: Vec = mints - .iter() - .map(|mint| { - let field_ident = &mint.field_ident; - quote! { self.#field_ident.to_account_info() } - }) - .collect(); - - // Authority signer check for mints without authority_seeds - let authority_signer_checks: Vec = mints - .iter() - .filter(|m| m.authority_seeds.is_none()) - .map(|mint| { - let authority = &mint.authority; - quote! { - if !self.#authority.to_account_info().is_signer { - return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature.into()); - } - } - }) - .collect(); - - quote! { - { - #output_tree_setup - - // Build SingleMintParams for each mint - #(#mint_params_builds)* - - // Array of mint params - let __mint_params: [light_account::SingleMintParams<'_>; #mint_count] = [ - #(#param_idents),* - ]; - - // Array of mint seed AccountInfos - let __mint_seed_accounts: [solana_account_info::AccountInfo<'info>; #mint_count] = [ - #(#mint_seed_account_exprs),* - ]; - - // Array of mint AccountInfos - let __mint_accounts: [solana_account_info::AccountInfo<'info>; #mint_count] = [ - #(#mint_account_exprs),* - ]; - - // Check authority signers for mints without authority_seeds - #(#authority_signer_checks)* - - // Build CreateMints struct and invoke - light_account::CreateMints { - mints: &__mint_params, - proof_data: &#proof_access, - mint_seed_accounts: &__mint_seed_accounts, - mint_accounts: &__mint_accounts, - static_accounts: light_account::CreateMintsStaticAccounts { - fee_payer: &self.#fee_payer.to_account_info(), - compressible_config: &self.#light_token_config.to_account_info(), - rent_sponsor: &self.#light_token_rent_sponsor.to_account_info(), - cpi_authority: &self.#light_token_cpi_authority.to_account_info(), - }, - cpi_context_offset: #cpi_context_offset, - } - .invoke(&cpi_accounts)?; - } - } -} diff --git a/sdk-libs/macros/src/light_pdas/accounts/token.rs b/sdk-libs/macros/src/light_pdas/accounts/token.rs index f66645ad0a..30b2034e93 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/token.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/token.rs @@ -1,279 +1,5 @@ -//! Light token account code generation. +//! Light token account types. //! -//! This module handles code generation for token account and ATA CPI invocations. -//! Parsing is handled by `light_account.rs`. -//! -//! ## Code Generation -//! -//! Token accounts and ATAs are created in `LightPreInit` (before instruction logic) -//! so they are available for use during the instruction handler (transfers, etc.). -//! -//! - **Token Accounts**: Use `CreateTokenAccountCpi` with PDA signing -//! - **ATAs**: Use `CreateTokenAtaCpi` with `idempotent()` builder -//! -//! ## Requirements -//! -//! Programs using `#[light_account(init, token, ...)]` must have a `crate::ID` -//! constant, which is the standard pattern when using Anchor's `declare_id!` macro. -//! The generated code passes `&crate::ID` to `CreateTokenAccountCpi::rent_free()` -//! for PDA signing verification. - -use proc_macro2::TokenStream; -use quote::quote; - -use super::{ - light_account::{AtaField, TokenAccountField}, - mint::InfraRefs, -}; - -/// Generate token account creation CPI code for a single token account field. -/// -/// Generated code uses `CreateTokenAccountCpi` with rent-free mode and PDA signing. -/// -/// Bump handling: -/// - If `bump` parameter is provided, uses that value -/// - If `bump` is not provided, auto-derives using `Pubkey::find_program_address()` -/// - Bump is always appended as the final seed in the signer seeds -#[allow(dead_code)] -pub(super) fn generate_token_account_cpi( - field: &TokenAccountField, - infra: &InfraRefs, -) -> Option { - // Only generate creation code if has_init is true - if !field.has_init { - return None; - } - - let field_ident = &field.field_ident; - let light_token_config = &infra.light_token_config; - let light_token_rent_sponsor = &infra.light_token_rent_sponsor; - let fee_payer = &infra.fee_payer; - - // Generate token account PDA seeds array from parsed seeds (WITHOUT bump - bump is added separately) - // These are the seeds for the token account itself (for PDA signing), NOT the authority seeds. - // Bind each seed to a local variable first, then call .as_ref() to avoid - // temporary lifetime issues (e.g., self.mint.key() creates a Pubkey that - // would be dropped before .as_ref() completes if done in one expression) - // - // User provides expressions WITHOUT bump in the array: - // seeds = [VAULT_SEED, self.mint.key()] - // Generates: - // let __seed_0 = VAULT_SEED; let __seed_0_ref: &[u8] = __seed_0.as_ref(); - // let __seed_1 = self.mint.key(); let __seed_1_ref: &[u8] = __seed_1.as_ref(); - // // bump is auto-derived or provided via bump parameter - // &[__seed_0_ref, __seed_1_ref, &[bump]] - let token_seeds = &field.seeds; - let seed_bindings: Vec = token_seeds - .iter() - .enumerate() - .map(|(i, seed)| { - let val_name = - syn::Ident::new(&format!("__seed_{}", i), proc_macro2::Span::call_site()); - let ref_name = - syn::Ident::new(&format!("__seed_{}_ref", i), proc_macro2::Span::call_site()); - quote! { - let #val_name = #seed; - let #ref_name: &[u8] = #val_name.as_ref(); - } - }) - .collect(); - let seed_refs: Vec = (0..token_seeds.len()) - .map(|i| { - let ref_name = - syn::Ident::new(&format!("__seed_{}_ref", i), proc_macro2::Span::call_site()); - quote! { #ref_name } - }) - .collect(); - - // Get bump - either from parameter or auto-derive using find_program_address - let bump_derivation = field - .bump - .as_ref() - .map(|b| quote! { let __bump: u8 = #b; }) - .unwrap_or_else(|| { - // Auto-derive bump from seeds - if token_seeds.is_empty() { - quote! { - let __bump: u8 = { - let (_, bump) = solana_pubkey::Pubkey::find_program_address(&[], &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); - bump - }; - } - } else { - quote! { - let __bump: u8 = { - let seeds: &[&[u8]] = &[#(#seed_refs),*]; - let (_, bump) = solana_pubkey::Pubkey::find_program_address(seeds, &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id)); - bump - }; - } - } - }); - - // Build seeds array with bump appended as final seed - let seeds_array_expr = if token_seeds.is_empty() { - quote! { &[&__bump_slice[..]] } - } else { - quote! { &[#(#seed_refs,)* &__bump_slice[..]] } - }; - - // Get mint binding from field or default - let mint_binding = field - .mint - .as_ref() - .map(|m| quote! { let __mint_info = self.#m.to_account_info(); }) - .unwrap_or_else(|| quote! { let __mint_info = self.mint.to_account_info(); }); - - // owner is [u8; 32] - the owner of the token account - let owner_expr = field - .owner - .as_ref() - .map(|o| quote! { self.#o.to_account_info().key.to_bytes() }) - .unwrap_or_else(|| quote! { self.fee_payer.to_account_info().key.to_bytes() }); - - Some(quote! { - // Create token account: #field_ident - { - use light_account::CreateTokenAccountCpi; - - // Bind seeds to local variables to extend temporary lifetimes - #(#seed_bindings)* - - // Get bump - either provided or auto-derived - #bump_derivation - let __bump_slice: [u8; 1] = [__bump]; - let __token_account_seeds: &[&[u8]] = #seeds_array_expr; - - // Bind account infos to local variables so we can pass references - let __payer_info = self.#fee_payer.to_account_info(); - let __account_info = self.#field_ident.to_account_info(); - #mint_binding - let __config_info = self.#light_token_config.to_account_info(); - let __sponsor_info = self.#light_token_rent_sponsor.to_account_info(); - - CreateTokenAccountCpi { - payer: &__payer_info, - account: &__account_info, - mint: &__mint_info, - owner: #owner_expr, - } - .rent_free( - &__config_info, - &__sponsor_info, - &__system_program, - &crate::LIGHT_CPI_SIGNER.program_id, - ) - .invoke_signed(__token_account_seeds)?; - } - }) -} - -/// Generate ATA creation CPI code for a single ATA field. -/// -/// Generated code uses `CreateTokenAtaCpi` builder with rent-free mode. -#[allow(dead_code)] -pub(super) fn generate_ata_cpi(field: &AtaField, infra: &InfraRefs) -> Option { - // Only generate creation code if has_init is true - if !field.has_init { - return None; - } - - let field_ident = &field.field_ident; - let owner = &field.owner; - let mint = &field.mint; - let light_token_config = &infra.light_token_config; - let light_token_rent_sponsor = &infra.light_token_rent_sponsor; - let fee_payer = &infra.fee_payer; - - Some(quote! { - // Create ATA: #field_ident - { - use light_account::CreateTokenAtaCpi; - - // Bind account infos to local variables so we can pass references - let __payer_info = self.#fee_payer.to_account_info(); - let __owner_info = self.#owner.to_account_info(); - let __mint_info = self.#mint.to_account_info(); - let __ata_info = self.#field_ident.to_account_info(); - let __config_info = self.#light_token_config.to_account_info(); - let __sponsor_info = self.#light_token_rent_sponsor.to_account_info(); - - CreateTokenAtaCpi { - payer: &__payer_info, - owner: &__owner_info, - mint: &__mint_info, - ata: &__ata_info, - } - .idempotent() - .rent_free( - &__config_info, - &__sponsor_info, - &__system_program, - ) - .invoke()?; - } - }) -} - -/// Builder for generating finalize code for token accounts and ATAs. -pub(super) struct TokenAccountsBuilder<'a> { - token_account_fields: &'a [TokenAccountField], - ata_fields: &'a [AtaField], - infra: &'a InfraRefs, -} - -impl<'a> TokenAccountsBuilder<'a> { - /// Create a new builder. - pub fn new( - token_account_fields: &'a [TokenAccountField], - ata_fields: &'a [AtaField], - infra: &'a InfraRefs, - ) -> Self { - Self { - token_account_fields, - ata_fields, - infra, - } - } - - /// Check if any token accounts or ATAs need to be created. - pub fn needs_creation(&self) -> bool { - self.token_account_fields.iter().any(|f| f.has_init) - || self.ata_fields.iter().any(|f| f.has_init) - } - - /// Generate token account and ATA creation code for pre_init. - /// - /// Returns None if no token accounts or ATAs need to be created. - /// Otherwise returns the CPI code (without Ok() return). - pub fn generate_pre_init_token_creation(&self) -> Option { - if !self.needs_creation() { - return None; - } - - // Generate token account creation code - let token_account_cpis: Vec = self - .token_account_fields - .iter() - .filter_map(|f| generate_token_account_cpi(f, self.infra)) - .collect(); - - // Generate ATA creation code - let ata_cpis: Vec = self - .ata_fields - .iter() - .filter_map(|f| generate_ata_cpi(f, self.infra)) - .collect(); - - Some(quote! { - // Get system program from the struct's system_program field - let __system_program = self.system_program.to_account_info(); - - // Create token accounts (in pre_init so they're available for instruction logic) - #(#token_account_cpis)* - - // Create ATAs (in pre_init so they're available for instruction logic) - #(#ata_cpis)* - }) - } -} +//! Token account and ATA field types are defined in `light_account.rs`. +//! Code generation for token/ATA creation now happens via the `create_accounts()` +//! SDK function, called from `builder.rs`. diff --git a/sdk-libs/sdk-types/src/interface/accounts/create_accounts.rs b/sdk-libs/sdk-types/src/interface/accounts/create_accounts.rs new file mode 100644 index 0000000000..4022e4fdfa --- /dev/null +++ b/sdk-libs/sdk-types/src/interface/accounts/create_accounts.rs @@ -0,0 +1,420 @@ +//! Reusable `create_accounts` function for creating PDAs, mints, token vaults, +//! and ATAs in a single instruction. Used by both `#[derive(LightAccounts)]` +//! macro-generated code and manual implementations. + +use alloc::{vec, vec::Vec}; + +use light_account_checks::AccountInfoTrait; +use light_compressed_account::{ + instruction_data::{ + cpi_context::CompressedCpiContext, + with_account_info::InstructionDataInvokeCpiWithAccountInfo, + }, + CpiSigner, +}; + +use crate::{ + cpi_accounts::{v2::CpiAccounts, CpiAccountsConfig}, + cpi_context_write::CpiContextWriteAccounts, + error::LightSdkTypesError, + interface::{ + accounts::init_compressed_account::prepare_compressed_account_on_init, + cpi::{ + create_mints::{CreateMints, CreateMintsStaticAccounts, SingleMintParams}, + create_token_accounts::{CreateTokenAccountCpi, CreateTokenAtaCpi}, + invoke::InvokeLightSystemProgram, + }, + create_accounts_proof::CreateAccountsProof, + program::config::LightConfig, + }, +}; + +// ============================================================================ +// Parameter structs +// ============================================================================ + +/// Parameters for a single PDA to initialize. +pub struct PdaInitParam<'a, AI: AccountInfoTrait> { + /// The PDA account to register as a compressed account. + pub account: &'a AI, +} + +/// Input for creating compressed mints. +/// +/// Uses owned arrays because `CreateMints` expects `&[AI]` slices, +/// and Anchor requires `.to_account_info()` conversion. +pub struct CreateMintsInput<'a, AI: AccountInfoTrait + Clone, const MINTS: usize> { + /// Per-mint parameters (decimals, authority, seeds, etc.). + pub params: [SingleMintParams<'a>; MINTS], + /// Mint seed accounts (signers) - one per mint. + pub mint_seed_accounts: [AI; MINTS], + /// Mint PDA accounts (writable) - one per mint. + pub mint_accounts: [AI; MINTS], +} + +/// Parameters for a single token vault to create. +pub struct TokenInitParam<'a, AI: AccountInfoTrait> { + /// The token vault account. + pub account: &'a AI, + /// The mint account for this vault. + pub mint: &'a AI, + /// Owner of the token account (as raw bytes). + pub owner: [u8; 32], + /// PDA seeds for the vault (with bump as last element). + pub seeds: &'a [&'a [u8]], +} + +/// Parameters for a single ATA to create. +pub struct AtaInitParam<'a, AI: AccountInfoTrait> { + /// The ATA account. + pub ata: &'a AI, + /// The owner of the ATA. + pub owner: &'a AI, + /// The mint for the ATA. + pub mint: &'a AI, + /// Whether to use idempotent creation. + pub idempotent: bool, +} + +/// Shared accounts needed across all account creation operations. +pub struct SharedAccounts<'a, AI: AccountInfoTrait> { + /// Fee payer for the transaction. + pub fee_payer: &'a AI, + /// CPI signer for the program. + pub cpi_signer: CpiSigner, + /// Proof data containing tree indices, validity proof, etc. + pub proof: &'a CreateAccountsProof, + /// Program ID (as raw bytes). + pub program_id: [u8; 32], + /// Compression config account. Required if PDAS > 0. + pub compression_config: Option<&'a AI>, + /// Compressible config account. Required if MINTS > 0 or TOKENS > 0 or ATAS > 0. + pub compressible_config: Option<&'a AI>, + /// Rent sponsor account. Required if MINTS > 0 or TOKENS > 0 or ATAS > 0. + pub rent_sponsor: Option<&'a AI>, + /// CPI authority account. Required if MINTS > 0. + pub cpi_authority: Option<&'a AI>, + /// System program account. Required if TOKENS > 0 or ATAS > 0. + pub system_program: Option<&'a AI>, +} + +// ============================================================================ +// Main function +// ============================================================================ + +/// Create compressed PDAs, mints, token vaults, and ATAs in a single instruction. +/// +/// Returns `true` if CPI context was used (PDAS > 0 && MINTS > 0), `false` otherwise. +/// +/// # Const Generics +/// +/// - `PDAS`: Number of compressed PDAs to register. +/// - `MINTS`: Number of compressed mints to create via `CreateMints`. +/// - `TOKENS`: Number of PDA token vaults to create via `CreateTokenAccountCpi`. +/// - `ATAS`: Number of ATAs to create via `CreateTokenAtaCpi`. +/// +/// # Type Parameters +/// +/// - `AI`: Account info type (`AccountInfoTrait`). +/// - `F`: Closure called after all PDAs are prepared, before CPI context write. +/// Signature: `FnOnce(&LightConfig, u64) -> Result<(), LightSdkTypesError>`. +/// The closure receives the loaded `LightConfig` and current slot. +/// When `PDAS = 0`, pass `|_, _| Ok(())`. +#[inline(never)] +#[allow(clippy::too_many_arguments)] +pub fn create_accounts< + AI: AccountInfoTrait + Clone, + const PDAS: usize, + const MINTS: usize, + const TOKENS: usize, + const ATAS: usize, + F: FnOnce(&LightConfig, u64) -> Result<(), LightSdkTypesError>, +>( + pdas: [PdaInitParam<'_, AI>; PDAS], + pda_setup: F, + mints: Option>, + tokens: [TokenInitParam<'_, AI>; TOKENS], + atas: [AtaInitParam<'_, AI>; ATAS], + shared: &SharedAccounts<'_, AI>, + remaining_accounts: &[AI], +) -> Result { + // ==================================================================== + // 1. Validate required Option fields based on const generics + // ==================================================================== + if PDAS > u8::MAX as usize || MINTS > u8::MAX as usize { + return Err(LightSdkTypesError::InvalidInstructionData); + } + if PDAS > 0 && shared.compression_config.is_none() { + return Err(LightSdkTypesError::InvalidInstructionData); + } + let has_tokens = MINTS > 0 || TOKENS > 0 || ATAS > 0; + if has_tokens && (shared.compressible_config.is_none() || shared.rent_sponsor.is_none()) { + return Err(LightSdkTypesError::InvalidInstructionData); + } + if MINTS > 0 && shared.cpi_authority.is_none() { + return Err(LightSdkTypesError::InvalidInstructionData); + } + if (TOKENS > 0 || ATAS > 0) && shared.system_program.is_none() { + return Err(LightSdkTypesError::InvalidInstructionData); + } + + // CPI context is needed whenever mints are created: + // - The client always packs a CPI context account for mint creation + // - Single mint (N=1): CPI context account is present but invoke_single_mint skips it + // - Multi-mint (N>1): CPI context used for batching (N-1 writes + 1 execute) + // - PDAs + mints: PDAs write to CPI context first, then mints execute with offset + let with_cpi_context = MINTS > 0; + + // ==================================================================== + // 2. Build CPI accounts + // ==================================================================== + let system_accounts_offset = shared.proof.system_accounts_offset as usize; + if remaining_accounts.len() < system_accounts_offset { + return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); + } + + let config = if with_cpi_context { + CpiAccountsConfig::new_with_cpi_context(shared.cpi_signer) + } else { + CpiAccountsConfig::new(shared.cpi_signer) + }; + let cpi_accounts = CpiAccounts::new_with_config( + shared.fee_payer, + &remaining_accounts[system_accounts_offset..], + config, + ); + + // ==================================================================== + // 3. Create PDAs + // ==================================================================== + if PDAS > 0 { + create_pdas(&pdas, pda_setup, shared, &cpi_accounts, with_cpi_context)?; + } + + // ==================================================================== + // 4. Create Mints + // ==================================================================== + if MINTS > 0 { + if let Some(mints_input) = mints { + create_mints_inner::(mints_input, shared, &cpi_accounts, PDAS as u8)?; + } else { + return Err(LightSdkTypesError::InvalidInstructionData); + } + } + + // ==================================================================== + // 5. Create Token Vaults + // ==================================================================== + if TOKENS > 0 { + create_token_vaults(&tokens, shared)?; + } + + // ==================================================================== + // 6. Create ATAs + // ==================================================================== + if ATAS > 0 { + create_atas(&atas, shared)?; + } + + Ok(with_cpi_context) +} + +// ============================================================================ +// Internal helpers +// ============================================================================ + +#[inline(never)] +fn create_pdas( + pdas: &[PdaInitParam<'_, AI>], + pda_setup: F, + shared: &SharedAccounts<'_, AI>, + cpi_accounts: &CpiAccounts<'_, AI>, + with_cpi_context: bool, +) -> Result<(), LightSdkTypesError> +where + F: FnOnce(&LightConfig, u64) -> Result<(), LightSdkTypesError>, +{ + let address_tree_info = &shared.proof.address_tree_info; + let address_tree_account = cpi_accounts + .get_tree_account_info(address_tree_info.address_merkle_tree_pubkey_index as usize)?; + let address_tree_pubkey = address_tree_account.key(); + let output_tree_index = shared.proof.output_state_tree_index; + + // Load config and get current slot + let compression_config = shared + .compression_config + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let light_config = LightConfig::load_checked(compression_config, &shared.program_id) + .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + let current_slot = + AI::get_current_slot().map_err(|_| LightSdkTypesError::InvalidInstructionData)?; + + // Prepare all PDAs + let cpi_context = if with_cpi_context { + CompressedCpiContext::first() + } else { + CompressedCpiContext::default() + }; + + let mut new_address_params = Vec::with_capacity(pdas.len()); + let mut account_infos = Vec::with_capacity(pdas.len()); + + for (i, pda) in pdas.iter().enumerate() { + let pda_key = pda.account.key(); + prepare_compressed_account_on_init( + &pda_key, + &address_tree_pubkey, + address_tree_info, + output_tree_index, + i as u8, + &shared.program_id, + &mut new_address_params, + &mut account_infos, + )?; + } + + // Call the user's setup closure (e.g., set_decompressed on each PDA) + pda_setup(&light_config, current_slot)?; + + // Build instruction data + let instruction_data = InstructionDataInvokeCpiWithAccountInfo { + mode: 1, // V2 mode + bump: shared.cpi_signer.bump, + invoking_program_id: shared.cpi_signer.program_id.into(), + compress_or_decompress_lamports: 0, + is_compress: false, + with_cpi_context, + with_transaction_hash: false, + cpi_context, + proof: shared.proof.proof.0, + new_address_params, + account_infos, + read_only_addresses: vec![], + read_only_accounts: vec![], + }; + + if with_cpi_context { + // Write to CPI context first (combined execution happens with mints) + let cpi_context_accounts = CpiContextWriteAccounts { + fee_payer: cpi_accounts.fee_payer(), + authority: cpi_accounts.authority()?, + cpi_context: cpi_accounts.cpi_context()?, + cpi_signer: shared.cpi_signer, + }; + instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; + } else { + // Direct invocation (no mints following) + instruction_data.invoke(cpi_accounts.clone())?; + } + + Ok(()) +} + +#[inline(never)] +fn create_mints_inner( + mints_input: CreateMintsInput<'_, AI, MINTS>, + shared: &SharedAccounts<'_, AI>, + cpi_accounts: &CpiAccounts<'_, AI>, + cpi_context_offset: u8, +) -> Result<(), LightSdkTypesError> { + let compressible_config = shared + .compressible_config + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let rent_sponsor = shared + .rent_sponsor + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let cpi_authority = shared + .cpi_authority + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + CreateMints { + mints: &mints_input.params, + proof_data: shared.proof, + mint_seed_accounts: &mints_input.mint_seed_accounts, + mint_accounts: &mints_input.mint_accounts, + static_accounts: CreateMintsStaticAccounts { + fee_payer: shared.fee_payer, + compressible_config, + rent_sponsor, + cpi_authority, + }, + cpi_context_offset, + } + .invoke(cpi_accounts) +} + +#[inline(never)] +fn create_token_vaults( + tokens: &[TokenInitParam<'_, AI>], + shared: &SharedAccounts<'_, AI>, +) -> Result<(), LightSdkTypesError> { + let compressible_config = shared + .compressible_config + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let rent_sponsor = shared + .rent_sponsor + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let system_program = shared + .system_program + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + for token in tokens { + CreateTokenAccountCpi { + payer: shared.fee_payer, + account: token.account, + mint: token.mint, + owner: token.owner, + } + .rent_free( + compressible_config, + rent_sponsor, + system_program, + &shared.program_id, + ) + .invoke_signed(token.seeds)?; + } + + Ok(()) +} + +#[inline(never)] +fn create_atas( + atas: &[AtaInitParam<'_, AI>], + shared: &SharedAccounts<'_, AI>, +) -> Result<(), LightSdkTypesError> { + let compressible_config = shared + .compressible_config + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let rent_sponsor = shared + .rent_sponsor + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + let system_program = shared + .system_program + .ok_or(LightSdkTypesError::InvalidInstructionData)?; + + for ata in atas { + if ata.idempotent { + CreateTokenAtaCpi { + payer: shared.fee_payer, + owner: ata.owner, + mint: ata.mint, + ata: ata.ata, + } + .idempotent() + .rent_free(compressible_config, rent_sponsor, system_program) + .invoke()?; + } else { + CreateTokenAtaCpi { + payer: shared.fee_payer, + owner: ata.owner, + mint: ata.mint, + ata: ata.ata, + } + .rent_free(compressible_config, rent_sponsor, system_program) + .invoke()?; + } + } + + Ok(()) +} diff --git a/sdk-libs/sdk-types/src/interface/accounts/mod.rs b/sdk-libs/sdk-types/src/interface/accounts/mod.rs index 3e313e9503..129cda1caa 100644 --- a/sdk-libs/sdk-types/src/interface/accounts/mod.rs +++ b/sdk-libs/sdk-types/src/interface/accounts/mod.rs @@ -5,3 +5,6 @@ pub mod finalize; pub mod init_compressed_account; + +#[cfg(all(feature = "cpi-context", feature = "token"))] +pub mod create_accounts; diff --git a/sdk-tests/anchor-manual-test/src/account_loader/derived_accounts.rs b/sdk-tests/anchor-manual-test/src/account_loader/derived_accounts.rs index f3bdc41516..0cff74e0f0 100644 --- a/sdk-tests/anchor-manual-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/anchor-manual-test/src/account_loader/derived_accounts.rs @@ -5,14 +5,12 @@ use anchor_lang::prelude::*; use light_account::{ + create_accounts, light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, - LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, LightSdkTypesError, + PackedLightAccountVariantTrait, PdaInitParam, SharedAccounts, }; +use solana_account_info::AccountInfo; use super::{ accounts::{CreateZeroCopy, CreateZeroCopyParams}, @@ -42,107 +40,36 @@ impl<'info> LightPreInit, CreateZeroCopyParams> for CreateZer remaining_accounts: &[AccountInfo<'info>], params: &CreateZeroCopyParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - use light_account::{LightAccount, LightConfig}; - use solana_program::{clock::Clock, sysvar::Sysvar}; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - const NUM_LIGHT_PDAS: usize = 1; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 3. Load config and get current slot - let light_config = - LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - // 4. Prepare compressed account using helper function - // Get the record's key from AccountLoader - let record_key = self.record.key(); - prepare_compressed_account_on_init( - &record_key.to_bytes(), - &address_tree_pubkey.to_bytes(), - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID.to_bytes(), - &mut new_address_params, - &mut account_infos, - )?; - - // 5. Set compression_info on the zero-copy record - // For AccountLoader, we need to use load_init() which was already called by Anchor - { + let record_info = self.record.to_account_info(); + + create_accounts::, 1, 0, 0, 0, _>( + [PdaInitParam { + account: &record_info, + }], + |light_config, current_slot| { let mut record = self .record .load_init() .map_err(|_| LightSdkTypesError::Borsh)?; - record.set_decompressed(&light_config, current_slot); - } - - // 6. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 7. Invoke Light System Program CPI - instruction_data.invoke(cpi_accounts)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - - Ok(false) // No mints, so no CPI context write - }; - inner() + record.set_decompressed(light_config, current_slot); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: &self.fee_payer.to_account_info(), + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::LIGHT_CPI_SIGNER.program_id, + compression_config: Some(&self.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/anchor-manual-test/src/all/derived.rs b/sdk-tests/anchor-manual-test/src/all/derived.rs index 92e263a285..49ad30b1b0 100644 --- a/sdk-tests/anchor-manual-test/src/all/derived.rs +++ b/sdk-tests/anchor-manual-test/src/all/derived.rs @@ -8,13 +8,8 @@ use anchor_lang::prelude::*; use light_account::{ - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - CreateMints, CreateMintsStaticAccounts, CreateTokenAccountCpi, CreateTokenAtaCpi, - InvokeLightSystemProgram, LightAccount, LightFinalize, LightPreInit, LightSdkTypesError, - PackedAddressTreeInfoExt, SingleMintParams, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + create_accounts, AtaInitParam, CreateMintsInput, LightAccount, LightFinalize, LightPreInit, + LightSdkTypesError, PdaInitParam, SharedAccounts, SingleMintParams, TokenInitParam, }; use solana_account_info::AccountInfo; @@ -32,219 +27,104 @@ impl<'info> LightPreInit, CreateAllParams> for CreateAllAccou remaining_accounts: &[AccountInfo<'info>], params: &CreateAllParams, ) -> std::result::Result { - let mut inner = || -> std::result::Result { - use light_account::LightConfig; - use solana_program::{clock::Clock, sysvar::Sysvar}; - - // Constants for this instruction - const NUM_LIGHT_PDAS: usize = 2; - const NUM_LIGHT_MINTS: usize = 1; - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true - - // ==================================================================== - // 1. Build CPI accounts with cpi_context config - // ==================================================================== - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // ==================================================================== - // 2. Get address tree info - // ==================================================================== - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - - // ==================================================================== - // 3. Load config, get current slot - // ==================================================================== - let light_config = - LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - // ==================================================================== - // 4. Create PDAs via invoke_write_to_cpi_context_first() - // ==================================================================== - { - // CPI context for PDAs - set to first() since we have mints coming after - let cpi_context = CompressedCpiContext::first(); - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 4a. Prepare Borsh PDA (index 0) - let borsh_record_key = self.borsh_record.key(); - prepare_compressed_account_on_init( - &borsh_record_key.to_bytes(), - &address_tree_pubkey.to_bytes(), - address_tree_info, - output_tree_index, - 0, // assigned_account_index = 0 - &crate::ID.to_bytes(), - &mut new_address_params, - &mut account_infos, - )?; + const NUM_LIGHT_PDAS: usize = 2; + const NUM_LIGHT_MINTS: usize = 1; + const NUM_TOKENS: usize = 1; + const NUM_ATAS: usize = 1; + + let authority_key = self.authority.key().to_bytes(); + let mint_signer_key = self.mint_signer.key().to_bytes(); + let mint_key = self.mint.key(); + + let mint_signer_seeds: &[&[u8]] = &[ + ALL_MINT_SIGNER_SEED, + authority_key.as_ref(), + &[params.mint_signer_bump], + ]; + + let vault_seeds: &[&[u8]] = &[ + ALL_TOKEN_VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; + + let payer_info = self.payer.to_account_info(); + let borsh_record_info = self.borsh_record.to_account_info(); + let zero_copy_record_info = self.zero_copy_record.to_account_info(); + let mint_info = self.mint.to_account_info(); + let token_vault_info = self.token_vault.to_account_info(); + let user_ata_info = self.user_ata.to_account_info(); + let system_program_info = self.system_program.to_account_info(); + + create_accounts::< + AccountInfo<'info>, + NUM_LIGHT_PDAS, + NUM_LIGHT_MINTS, + NUM_TOKENS, + NUM_ATAS, + _, + >( + [ + PdaInitParam { + account: &borsh_record_info, + }, + PdaInitParam { + account: &zero_copy_record_info, + }, + ], + |light_config, current_slot| { + // Set compression_info on the Borsh record self.borsh_record - .set_decompressed(&light_config, current_slot); - - // 4b. Prepare ZeroCopy PDA (index 1) - let zero_copy_record_key = self.zero_copy_record.key(); - prepare_compressed_account_on_init( - &zero_copy_record_key.to_bytes(), - &address_tree_pubkey.to_bytes(), - address_tree_info, - output_tree_index, - 1, // assigned_account_index = 1 - &crate::ID.to_bytes(), - &mut new_address_params, - &mut account_infos, - )?; + .set_decompressed(light_config, current_slot); + // Set compression_info on the ZeroCopy record { let mut record = self .zero_copy_record .load_init() .map_err(|_| LightSdkTypesError::Borsh)?; - record.set_decompressed(&light_config, current_slot); + record.set_decompressed(light_config, current_slot); } - - // 4c. Build instruction data and write to CPI context (doesn't execute yet) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - // Write to CPI context first (combined execution happens with mints) - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - - // ==================================================================== - // 5. Create Mint via CreateMints with cpi_context_offset - // ==================================================================== - { - let authority = self.authority.key(); - let mint_signer_key = self.mint_signer.key(); - - let mint_signer_seeds: &[&[u8]] = &[ - ALL_MINT_SIGNER_SEED, - authority.as_ref(), - &[params.mint_signer_bump], - ]; - - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + Ok(()) + }, + Some(CreateMintsInput { + params: [SingleMintParams { decimals: 6, - mint_authority: authority.to_bytes(), + mint_authority: authority_key, mint_bump: None, freeze_authority: None, - mint_seed_pubkey: mint_signer_key.to_bytes(), + mint_seed_pubkey: mint_signer_key, authority_seeds: None, mint_signer_seeds: Some(mint_signer_seeds), token_metadata: None, - }]; - - let payer_info = self.payer.to_account_info(); - let mint_seed_accounts = [self.mint_signer.to_account_info()]; - let mint_accounts = [self.mint.to_account_info()]; - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: &mint_seed_accounts, - mint_accounts: &mint_accounts, - static_accounts: CreateMintsStaticAccounts { - fee_payer: &payer_info, - compressible_config: &self.compressible_config, - rent_sponsor: &self.rent_sponsor, - cpi_authority: &self.cpi_authority, - }, - cpi_context_offset: NUM_LIGHT_PDAS as u8, - } - .invoke(&cpi_accounts)?; - } - - // ==================================================================== - // 6. Create Token Vault via CreateTokenAccountCpi - // ==================================================================== - { - let mint_key = self.mint.key(); - let vault_seeds: &[&[u8]] = &[ - ALL_TOKEN_VAULT_SEED, - mint_key.as_ref(), - &[params.token_vault_bump], - ]; - - let payer_info = self.payer.to_account_info(); - let token_vault_info = self.token_vault.to_account_info(); - let mint_info = self.mint.to_account_info(); - let system_program_info = self.system_program.to_account_info(); - CreateTokenAccountCpi { - payer: &payer_info, - account: &token_vault_info, - mint: &mint_info, - owner: self.vault_owner.key.to_bytes(), - } - .rent_free( - &self.compressible_config, - &self.rent_sponsor, - &system_program_info, - &crate::ID.to_bytes(), - ) - .invoke_signed(vault_seeds)?; - } - - // ==================================================================== - // 7. Create ATA via CreateTokenAtaCpi - // ==================================================================== - { - let payer_info = self.payer.to_account_info(); - let mint_info = self.mint.to_account_info(); - let user_ata_info = self.user_ata.to_account_info(); - let system_program_info = self.system_program.to_account_info(); - CreateTokenAtaCpi { - payer: &payer_info, - owner: &self.ata_owner, - mint: &mint_info, - ata: &user_ata_info, - } - .rent_free( - &self.compressible_config, - &self.rent_sponsor, - &system_program_info, - ) - .invoke()?; - } - - Ok(WITH_CPI_CONTEXT) - }; - inner() + }], + mint_seed_accounts: [self.mint_signer.to_account_info()], + mint_accounts: [mint_info.clone()], + }), + [TokenInitParam { + account: &token_vault_info, + mint: &mint_info, + owner: self.vault_owner.key.to_bytes(), + seeds: vault_seeds, + }], + [AtaInitParam { + ata: &user_ata_info, + owner: &self.ata_owner, + mint: &mint_info, + idempotent: false, + }], + &SharedAccounts { + fee_payer: &payer_info, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::LIGHT_CPI_SIGNER.program_id, + compression_config: Some(&self.compression_config), + compressible_config: Some(&self.compressible_config), + rent_sponsor: Some(&self.rent_sponsor), + cpi_authority: Some(&self.cpi_authority), + system_program: Some(&system_program_info), + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/anchor-manual-test/src/pda/derived_accounts.rs b/sdk-tests/anchor-manual-test/src/pda/derived_accounts.rs index d60736fea6..95c7855c45 100644 --- a/sdk-tests/anchor-manual-test/src/pda/derived_accounts.rs +++ b/sdk-tests/anchor-manual-test/src/pda/derived_accounts.rs @@ -1,13 +1,11 @@ use anchor_lang::prelude::*; use light_account::{ + create_accounts, light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, - LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, LightSdkTypesError, + PackedLightAccountVariantTrait, PdaInitParam, SharedAccounts, }; +use solana_account_info::AccountInfo; use super::{ accounts::{CreatePda, CreatePdaParams}, @@ -38,109 +36,32 @@ impl<'info> LightPreInit, CreatePdaParams> for CreatePda<'inf remaining_accounts: &[AccountInfo<'info>], params: &CreatePdaParams, ) -> std::result::Result { - let mut inner = || -> std::result::Result { - use light_account::{LightAccount, LightConfig}; - use solana_program::{clock::Clock, sysvar::Sysvar}; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - - const NUM_LIGHT_PDAS: usize = 1; - - // 6. Set compression_info from config - let light_config = - LightConfig::load_checked(&self.compression_config, &crate::ID.to_bytes()) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 - // ===================================================================== - { - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - // 3. Prepare compressed account using helper function - // Dynamic code 0-N variants depending on the accounts struct - // ===================================================================== - prepare_compressed_account_on_init( - &self.record.key().to_bytes(), - &address_tree_pubkey.to_bytes(), - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID.to_bytes(), - &mut new_address_params, - &mut account_infos, - )?; - self.record.set_decompressed(&light_config, current_slot); - // ===================================================================== - - // current_account_index += 1; - // For multiple accounts, repeat the pattern: - // let prepared2 = prepare_compressed_account_on_init(..., current_account_index, ...)?; - // current_account_index += 1; - - // 4. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 5. Invoke Light System Program CPI - instruction_data.invoke(cpi_accounts)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - // The authority and cpi_context accounts must be provided in remaining_accounts. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - } - // ===================================================================== - Ok(false) // No mints, so no CPI context write - }; - inner() + let record_info = self.record.to_account_info(); + + create_accounts::, 1, 0, 0, 0, _>( + [PdaInitParam { + account: &record_info, + }], + |light_config, current_slot| { + self.record.set_decompressed(light_config, current_slot); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: &self.fee_payer.to_account_info(), + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::LIGHT_CPI_SIGNER.program_id, + compression_config: Some(&self.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/anchor-manual-test/src/two_mints/derived.rs b/sdk-tests/anchor-manual-test/src/two_mints/derived.rs index d008dfcfa5..26cb947e9e 100644 --- a/sdk-tests/anchor-manual-test/src/two_mints/derived.rs +++ b/sdk-tests/anchor-manual-test/src/two_mints/derived.rs @@ -3,8 +3,8 @@ use anchor_lang::prelude::*; use light_account::{ - CpiAccounts, CpiAccountsConfig, CreateMints, CreateMintsStaticAccounts, LightFinalize, - LightPreInit, LightSdkTypesError, SingleMintParams, + create_accounts, CreateMintsInput, LightFinalize, LightPreInit, LightSdkTypesError, + SharedAccounts, SingleMintParams, }; use solana_account_info::AccountInfo; @@ -24,91 +24,70 @@ impl<'info> LightPreInit, CreateDerivedMintsParams> remaining_accounts: &[AccountInfo<'info>], params: &CreateDerivedMintsParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - // 1. Build CPI accounts - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - &self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); + let authority = self.authority.key().to_bytes(); + let mint_signer_0 = self.mint_signer_0.key().to_bytes(); + let mint_signer_1 = self.mint_signer_1.key().to_bytes(); - // Constants - const NUM_LIGHT_MINTS: usize = 2; - const NUM_LIGHT_PDAS: usize = 0; - #[allow(clippy::absurd_extreme_comparisons)] - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; + let mint_signer_0_seeds: &[&[u8]] = &[ + MINT_SIGNER_0_SEED, + authority.as_ref(), + &[params.mint_signer_0_bump], + ]; + let mint_signer_1_seeds: &[&[u8]] = &[ + MINT_SIGNER_1_SEED, + authority.as_ref(), + &[params.mint_signer_1_bump], + ]; - // 2. Build mint params - let authority = self.authority.key(); - let mint_signer_0 = self.mint_signer_0.key(); - let mint_signer_1 = self.mint_signer_1.key(); + let payer_info = self.payer.to_account_info(); - let mint_signer_0_seeds: &[&[u8]] = &[ - MINT_SIGNER_0_SEED, - authority.as_ref(), - &[params.mint_signer_0_bump], - ]; - let mint_signer_1_seeds: &[&[u8]] = &[ - MINT_SIGNER_1_SEED, - authority.as_ref(), - &[params.mint_signer_1_bump], - ]; - - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ - SingleMintParams { - decimals: 6, - mint_authority: authority.to_bytes(), - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_0.to_bytes(), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_0_seeds), - token_metadata: None, - }, - SingleMintParams { - decimals: 9, - mint_authority: authority.to_bytes(), - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_1.to_bytes(), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_1_seeds), - token_metadata: None, - }, - ]; - - // 3. Create mints - let payer_info = self.payer.to_account_info(); - let mint_seed_accounts = [ - self.mint_signer_0.to_account_info(), - self.mint_signer_1.to_account_info(), - ]; - let mint_accounts = [self.mint_0.to_account_info(), self.mint_1.to_account_info()]; - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: &mint_seed_accounts, - mint_accounts: &mint_accounts, - static_accounts: CreateMintsStaticAccounts { - fee_payer: &payer_info, - compressible_config: &self.compressible_config, - rent_sponsor: &self.rent_sponsor, - cpi_authority: &self.cpi_authority, - }, - cpi_context_offset: NUM_LIGHT_PDAS as u8, - } - .invoke(&cpi_accounts)?; - - Ok(WITH_CPI_CONTEXT) - }; - inner() + create_accounts::, 0, 2, 0, 0, _>( + [], + |_, _| Ok(()), + Some(CreateMintsInput { + params: [ + SingleMintParams { + decimals: 6, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_0, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_0_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 9, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_1, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_1_seeds), + token_metadata: None, + }, + ], + mint_seed_accounts: [ + self.mint_signer_0.to_account_info(), + self.mint_signer_1.to_account_info(), + ], + mint_accounts: [self.mint_0.to_account_info(), self.mint_1.to_account_info()], + }), + [], + [], + &SharedAccounts { + fee_payer: &payer_info, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::LIGHT_CPI_SIGNER.program_id, + compression_config: None, + compressible_config: Some(&self.compressible_config), + rent_sponsor: Some(&self.rent_sponsor), + cpi_authority: Some(&self.cpi_authority), + system_program: None, + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/pinocchio-light-program-test/src/account_loader/processor.rs b/sdk-tests/pinocchio-light-program-test/src/account_loader/processor.rs index 39c606f669..680bf5212e 100644 --- a/sdk-tests/pinocchio-light-program-test/src/account_loader/processor.rs +++ b/sdk-tests/pinocchio-light-program-test/src/account_loader/processor.rs @@ -1,12 +1,7 @@ use light_account_pinocchio::{ - prepare_compressed_account_on_init, CompressedCpiContext, CpiAccounts, CpiAccountsConfig, - InstructionDataInvokeCpiWithAccountInfo, InvokeLightSystemProgram, LightAccount, LightConfig, - LightSdkTypesError, PackedAddressTreeInfoExt, -}; -use pinocchio::{ - account_info::AccountInfo, - sysvars::{clock::Clock, Sysvar}, + create_accounts, LightAccount, LightSdkTypesError, PdaInitParam, SharedAccounts, }; +use pinocchio::account_info::AccountInfo; use super::accounts::{CreateZeroCopyRecord, CreateZeroCopyRecordParams}; use crate::state::ZeroCopyRecord; @@ -16,72 +11,36 @@ pub fn process( params: &CreateZeroCopyRecordParams, remaining_accounts: &[AccountInfo], ) -> Result<(), LightSdkTypesError> { - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - let cpi_context = CompressedCpiContext::default(); - let mut new_address_params = Vec::with_capacity(1); - let mut account_infos = Vec::with_capacity(1); - - let light_config = LightConfig::load_checked(ctx.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - let record_key = *ctx.record.key(); - prepare_compressed_account_on_init( - &record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, + let record = ctx.record; + + create_accounts::( + [PdaInitParam { + account: ctx.record, + }], + |light_config, current_slot| { + let mut account_data = record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(light_config, current_slot); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: ctx.fee_payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(ctx.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, )?; - - // Set compression_info on the zero-copy record via bytemuck - { - let mut account_data = ctx - .record - .try_borrow_mut_data() - .map_err(|_| LightSdkTypesError::Borsh)?; - let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; - let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); - record.set_decompressed(&light_config, current_slot); - } - - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: false, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - instruction_data.invoke(cpi_accounts)?; Ok(()) } diff --git a/sdk-tests/pinocchio-light-program-test/src/all/processor.rs b/sdk-tests/pinocchio-light-program-test/src/all/processor.rs index ba2dc8b579..801acdab07 100644 --- a/sdk-tests/pinocchio-light-program-test/src/all/processor.rs +++ b/sdk-tests/pinocchio-light-program-test/src/all/processor.rs @@ -1,13 +1,8 @@ use light_account_pinocchio::{ - prepare_compressed_account_on_init, CompressedCpiContext, CpiAccounts, CpiAccountsConfig, - CpiContextWriteAccounts, CreateMints, CreateMintsStaticAccounts, CreateTokenAccountCpi, - CreateTokenAtaCpi, InstructionDataInvokeCpiWithAccountInfo, InvokeLightSystemProgram, - LightAccount, LightConfig, LightSdkTypesError, PackedAddressTreeInfoExt, SingleMintParams, -}; -use pinocchio::{ - account_info::AccountInfo, - sysvars::{clock::Clock, Sysvar}, + create_accounts, AtaInitParam, CreateMintsInput, LightAccount, LightSdkTypesError, + PdaInitParam, SharedAccounts, SingleMintParams, TokenInitParam, }; +use pinocchio::account_info::AccountInfo; use super::accounts::{CreateAllAccounts, CreateAllParams}; @@ -20,190 +15,100 @@ pub fn process( const NUM_LIGHT_PDAS: usize = 2; const NUM_LIGHT_MINTS: usize = 1; - const WITH_CPI_CONTEXT: bool = true; - - // 1. Build CPI accounts - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Address tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - - // 3. Load config, get slot - let light_config = LightConfig::load_checked(ctx.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - // 4. Create PDAs via invoke_write_to_cpi_context_first - { - let cpi_context = CompressedCpiContext::first(); - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 4a. Borsh PDA (index 0) - let borsh_record_key = *ctx.borsh_record.key(); - prepare_compressed_account_on_init( - &borsh_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 0, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - { - let mut account_data = ctx - .borsh_record - .try_borrow_mut_data() - .map_err(|_| LightSdkTypesError::Borsh)?; - let mut record = crate::state::MinimalRecord::try_from_slice(&account_data[8..]) - .map_err(|_| LightSdkTypesError::Borsh)?; - record.set_decompressed(&light_config, current_slot); - let serialized = borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; - account_data[8..8 + serialized.len()].copy_from_slice(&serialized); - } - - // 4b. ZeroCopy PDA (index 1) - let zero_copy_record_key = *ctx.zero_copy_record.key(); - prepare_compressed_account_on_init( - &zero_copy_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 1, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - { - let mut account_data = ctx - .zero_copy_record - .try_borrow_mut_data() - .map_err(|_| LightSdkTypesError::Borsh)?; - let record_bytes = - &mut account_data[8..8 + core::mem::size_of::()]; - let record: &mut crate::state::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); - record.set_decompressed(&light_config, current_slot); - } - - // 4c. Write to CPI context - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - - // 5. Create Mint - { - let authority_key = *ctx.authority.key(); - let mint_signer_key = *ctx.mint_signer.key(); - - let mint_signer_seeds: &[&[u8]] = &[ - crate::MINT_SIGNER_SEED_A, - authority_key.as_ref(), - &[params.mint_signer_bump], - ]; - - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { - decimals: 9, - mint_authority: authority_key, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_key, - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_seeds), - token_metadata: None, - }]; - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: ctx.mint_signers_slice, - mint_accounts: ctx.mints_slice, - static_accounts: CreateMintsStaticAccounts { - fee_payer: ctx.payer, - compressible_config: ctx.compressible_config, - rent_sponsor: ctx.rent_sponsor, - cpi_authority: ctx.cpi_authority, + const NUM_TOKENS: usize = 1; + const NUM_ATAS: usize = 1; + + let authority_key = *ctx.authority.key(); + let mint_signer_key = *ctx.mint_signer.key(); + let mint_key = *ctx.mint.key(); + + let mint_signer_seeds: &[&[u8]] = &[ + crate::MINT_SIGNER_SEED_A, + authority_key.as_ref(), + &[params.mint_signer_bump], + ]; + + let vault_seeds: &[&[u8]] = &[ + crate::VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; + + let borsh_record = ctx.borsh_record; + let zero_copy_record = ctx.zero_copy_record; + + create_accounts::( + [ + PdaInitParam { + account: ctx.borsh_record, }, - cpi_context_offset: NUM_LIGHT_PDAS as u8, - } - .invoke(&cpi_accounts)?; - } - - // 6. Create Token Vault - { - let mint_key = *ctx.mint.key(); - let vault_seeds: &[&[u8]] = &[ - crate::VAULT_SEED, - mint_key.as_ref(), - &[params.token_vault_bump], - ]; - - CreateTokenAccountCpi { - payer: ctx.payer, + PdaInitParam { + account: ctx.zero_copy_record, + }, + ], + |light_config, current_slot| { + // Set compression_info on the Borsh record + { + let mut account_data = borsh_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let mut record = crate::state::MinimalRecord::try_from_slice(&account_data[8..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + record.set_decompressed(light_config, current_slot); + let serialized = borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; + account_data[8..8 + serialized.len()].copy_from_slice(&serialized); + } + // Set compression_info on the ZeroCopy record + { + let mut account_data = zero_copy_record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record_bytes = + &mut account_data[8..8 + core::mem::size_of::()]; + let record: &mut crate::state::ZeroCopyRecord = + bytemuck::from_bytes_mut(record_bytes); + record.set_decompressed(light_config, current_slot); + } + Ok(()) + }, + Some(CreateMintsInput { + params: [SingleMintParams { + decimals: 9, + mint_authority: authority_key, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }], + mint_seed_accounts: [ctx.mint_signers_slice[0]], + mint_accounts: [ctx.mints_slice[0]], + }), + [TokenInitParam { account: ctx.token_vault, mint: ctx.mint, owner: *ctx.vault_owner.key(), - } - .rent_free( - ctx.compressible_config, - ctx.rent_sponsor, - ctx.system_program, - &crate::ID, - ) - .invoke_signed(vault_seeds)?; - } - - // 7. Create ATA - { - CreateTokenAtaCpi { - payer: ctx.payer, + seeds: vault_seeds, + }], + [AtaInitParam { + ata: ctx.user_ata, owner: ctx.ata_owner, mint: ctx.mint, - ata: ctx.user_ata, - } - .rent_free( - ctx.compressible_config, - ctx.rent_sponsor, - ctx.system_program, - ) - .invoke()?; - } - + idempotent: false, + }], + &SharedAccounts { + fee_payer: ctx.payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(ctx.compression_config), + compressible_config: Some(ctx.compressible_config), + rent_sponsor: Some(ctx.rent_sponsor), + cpi_authority: Some(ctx.cpi_authority), + system_program: Some(ctx.system_program), + }, + remaining_accounts, + )?; Ok(()) } diff --git a/sdk-tests/pinocchio-light-program-test/src/mint/processor.rs b/sdk-tests/pinocchio-light-program-test/src/mint/processor.rs index bbdef81a1c..55ccd9baba 100644 --- a/sdk-tests/pinocchio-light-program-test/src/mint/processor.rs +++ b/sdk-tests/pinocchio-light-program-test/src/mint/processor.rs @@ -1,6 +1,5 @@ use light_account_pinocchio::{ - CpiAccounts, CpiAccountsConfig, CreateMints, CreateMintsStaticAccounts, LightSdkTypesError, - SingleMintParams, + create_accounts, CreateMintsInput, LightSdkTypesError, SharedAccounts, SingleMintParams, }; use pinocchio::account_info::AccountInfo; @@ -11,17 +10,6 @@ pub fn process( params: &CreateMintParams, remaining_accounts: &[AccountInfo], ) -> Result<(), LightSdkTypesError> { - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - let authority = *ctx.authority.key(); let mint_signer_key = *ctx.mint_signer.key(); @@ -31,32 +19,37 @@ pub fn process( &[params.mint_signer_bump], ]; - let sdk_mints: [SingleMintParams<'_>; 1] = [SingleMintParams { - decimals: 9, - mint_authority: authority, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_key, - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_seeds), - token_metadata: None, - }]; - - let mint_signers = core::slice::from_ref(ctx.mint_signer); - let mints = core::slice::from_ref(ctx.mint); - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: mint_signers, - mint_accounts: mints, - static_accounts: CreateMintsStaticAccounts { + create_accounts::( + [], + |_, _| Ok(()), + Some(CreateMintsInput { + params: [SingleMintParams { + decimals: 9, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_key, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_seeds), + token_metadata: None, + }], + mint_seed_accounts: [*ctx.mint_signer], + mint_accounts: [*ctx.mint], + }), + [], + [], + &SharedAccounts { fee_payer: ctx.payer, - compressible_config: ctx.compressible_config, - rent_sponsor: ctx.rent_sponsor, - cpi_authority: ctx.cpi_authority, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: None, + compressible_config: Some(ctx.compressible_config), + rent_sponsor: Some(ctx.rent_sponsor), + cpi_authority: Some(ctx.cpi_authority), + system_program: None, }, - cpi_context_offset: 0, - } - .invoke(&cpi_accounts) + remaining_accounts, + )?; + Ok(()) } diff --git a/sdk-tests/pinocchio-light-program-test/src/pda/processor.rs b/sdk-tests/pinocchio-light-program-test/src/pda/processor.rs index c902348148..8d990dfb3a 100644 --- a/sdk-tests/pinocchio-light-program-test/src/pda/processor.rs +++ b/sdk-tests/pinocchio-light-program-test/src/pda/processor.rs @@ -1,12 +1,7 @@ use light_account_pinocchio::{ - prepare_compressed_account_on_init, CompressedCpiContext, CpiAccounts, CpiAccountsConfig, - InstructionDataInvokeCpiWithAccountInfo, InvokeLightSystemProgram, LightAccount, LightConfig, - LightSdkTypesError, PackedAddressTreeInfoExt, -}; -use pinocchio::{ - account_info::AccountInfo, - sysvars::{clock::Clock, Sysvar}, + create_accounts, LightAccount, LightSdkTypesError, PdaInitParam, SharedAccounts, }; +use pinocchio::account_info::AccountInfo; use super::accounts::{CreatePda, CreatePdaParams}; @@ -15,75 +10,39 @@ pub fn process( params: &CreatePdaParams, remaining_accounts: &[AccountInfo], ) -> Result<(), LightSdkTypesError> { - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - let cpi_context = CompressedCpiContext::default(); - let mut new_address_params = Vec::with_capacity(1); - let mut account_infos = Vec::with_capacity(1); - - let light_config = LightConfig::load_checked(ctx.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - let record_key = *ctx.record.key(); - prepare_compressed_account_on_init( - &record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, + let record = ctx.record; + + create_accounts::( + [PdaInitParam { + account: ctx.record, + }], + |light_config, current_slot| { + use borsh::BorshDeserialize; + let mut account_data = record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let mut record = crate::state::MinimalRecord::try_from_slice(&account_data[8..]) + .map_err(|_| LightSdkTypesError::Borsh)?; + record.set_decompressed(light_config, current_slot); + let serialized = borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; + account_data[8..8 + serialized.len()].copy_from_slice(&serialized); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: ctx.fee_payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(ctx.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, )?; - - // Set compression_info on the record via borsh deserialize/serialize - { - use borsh::BorshDeserialize; - let mut account_data = ctx - .record - .try_borrow_mut_data() - .map_err(|_| LightSdkTypesError::Borsh)?; - let mut record = crate::state::MinimalRecord::try_from_slice(&account_data[8..]) - .map_err(|_| LightSdkTypesError::Borsh)?; - record.set_decompressed(&light_config, current_slot); - let serialized = borsh::to_vec(&record).map_err(|_| LightSdkTypesError::Borsh)?; - account_data[8..8 + serialized.len()].copy_from_slice(&serialized); - } - - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: false, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - instruction_data.invoke(cpi_accounts)?; Ok(()) } diff --git a/sdk-tests/pinocchio-light-program-test/src/two_mints/processor.rs b/sdk-tests/pinocchio-light-program-test/src/two_mints/processor.rs index c7c155216f..40bec3eafc 100644 --- a/sdk-tests/pinocchio-light-program-test/src/two_mints/processor.rs +++ b/sdk-tests/pinocchio-light-program-test/src/two_mints/processor.rs @@ -1,6 +1,5 @@ use light_account_pinocchio::{ - CpiAccounts, CpiAccountsConfig, CreateMints, CreateMintsStaticAccounts, LightSdkTypesError, - SingleMintParams, + create_accounts, CreateMintsInput, LightSdkTypesError, SharedAccounts, SingleMintParams, }; use pinocchio::account_info::AccountInfo; @@ -11,17 +10,6 @@ pub fn process( params: &CreateTwoMintsParams, remaining_accounts: &[AccountInfo], ) -> Result<(), LightSdkTypesError> { - let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - ctx.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - let authority = *ctx.authority.key(); let mint_signer_a_seeds: &[&[u8]] = &[ @@ -35,41 +23,49 @@ pub fn process( &[params.mint_signer_bump_b], ]; - let sdk_mints: [SingleMintParams<'_>; 2] = [ - SingleMintParams { - decimals: 9, - mint_authority: authority, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: *ctx.mint_signer_a.key(), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_a_seeds), - token_metadata: None, - }, - SingleMintParams { - decimals: 6, - mint_authority: authority, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: *ctx.mint_signer_b.key(), - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_b_seeds), - token_metadata: None, - }, - ]; - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: ctx.mint_signers_slice, - mint_accounts: ctx.mints_slice, - static_accounts: CreateMintsStaticAccounts { + create_accounts::( + [], + |_, _| Ok(()), + Some(CreateMintsInput { + params: [ + SingleMintParams { + decimals: 9, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: *ctx.mint_signer_a.key(), + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_a_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 6, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: *ctx.mint_signer_b.key(), + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_b_seeds), + token_metadata: None, + }, + ], + mint_seed_accounts: [ctx.mint_signers_slice[0], ctx.mint_signers_slice[1]], + mint_accounts: [ctx.mints_slice[0], ctx.mints_slice[1]], + }), + [], + [], + &SharedAccounts { fee_payer: ctx.payer, - compressible_config: ctx.compressible_config, - rent_sponsor: ctx.rent_sponsor, - cpi_authority: ctx.cpi_authority, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: None, + compressible_config: Some(ctx.compressible_config), + rent_sponsor: Some(ctx.rent_sponsor), + cpi_authority: Some(ctx.cpi_authority), + system_program: None, }, - cpi_context_offset: 0, - } - .invoke(&cpi_accounts) + remaining_accounts, + )?; + Ok(()) } diff --git a/sdk-tests/pinocchio-manual-test/src/account_loader/derived_accounts.rs b/sdk-tests/pinocchio-manual-test/src/account_loader/derived_accounts.rs index 2732bd233e..1eab65b394 100644 --- a/sdk-tests/pinocchio-manual-test/src/account_loader/derived_accounts.rs +++ b/sdk-tests/pinocchio-manual-test/src/account_loader/derived_accounts.rs @@ -5,13 +5,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_account_pinocchio::{ + create_accounts, light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, - LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, LightSdkTypesError, + PackedLightAccountVariantTrait, PdaInitParam, SharedAccounts, }; use pinocchio::account_info::AccountInfo; @@ -43,108 +40,37 @@ impl LightPreInit for CreateZeroCopy<'_> { remaining_accounts: &[AccountInfo], params: &CreateZeroCopyParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - use light_account_pinocchio::{LightAccount, LightConfig}; - use pinocchio::sysvars::{clock::Clock, Sysvar}; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - const NUM_LIGHT_PDAS: usize = 1; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 3. Load config and get current slot - let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - // 4. Prepare compressed account using helper function - // Get the record's key from AccountInfo - let record_key = *self.record.key(); - prepare_compressed_account_on_init( - &record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - - // 5. Set compression_info on the zero-copy record - // For pinocchio, access data directly via try_borrow_mut_data() + bytemuck - { - let mut account_data = self - .record + let zero_copy_record = self.record; + + create_accounts::( + [PdaInitParam { + account: self.record, + }], + |light_config, current_slot| { + let mut account_data = zero_copy_record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; let record_bytes = &mut account_data[8..8 + core::mem::size_of::()]; let record: &mut ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); - record.set_decompressed(&light_config, current_slot); - } - - // 6. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 7. Invoke Light System Program CPI - instruction_data.invoke(cpi_accounts)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - - Ok(false) // No mints, so no CPI context write - }; - inner() + record.set_decompressed(light_config, current_slot); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: self.fee_payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(self.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/pinocchio-manual-test/src/all/derived.rs b/sdk-tests/pinocchio-manual-test/src/all/derived.rs index 890eb14156..56cf9cec48 100644 --- a/sdk-tests/pinocchio-manual-test/src/all/derived.rs +++ b/sdk-tests/pinocchio-manual-test/src/all/derived.rs @@ -7,13 +7,8 @@ //! - 1 ATA via `CreateTokenAtaCpi` use light_account_pinocchio::{ - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - CreateMints, CreateMintsStaticAccounts, CreateTokenAccountCpi, CreateTokenAtaCpi, - InvokeLightSystemProgram, LightAccount, LightFinalize, LightPreInit, LightSdkTypesError, - PackedAddressTreeInfoExt, SingleMintParams, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + create_accounts, AtaInitParam, CreateMintsInput, LightAccount, LightFinalize, LightPreInit, + LightSdkTypesError, PdaInitParam, SharedAccounts, SingleMintParams, TokenInitParam, }; use pinocchio::account_info::AccountInfo; @@ -31,145 +26,65 @@ impl LightPreInit for CreateAllAccounts<'_> { remaining_accounts: &[AccountInfo], params: &CreateAllParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - use light_account_pinocchio::LightConfig; - use pinocchio::sysvars::{clock::Clock, Sysvar}; - - // Constants for this instruction - const NUM_LIGHT_PDAS: usize = 2; - const NUM_LIGHT_MINTS: usize = 1; - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; // true - - // ==================================================================== - // 1. Build CPI accounts with cpi_context config - // ==================================================================== - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // ==================================================================== - // 2. Get address tree info - // ==================================================================== - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - - // ==================================================================== - // 3. Load config, get current slot - // ==================================================================== - let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - - // ==================================================================== - // 4. Create PDAs via invoke_write_to_cpi_context_first() - // ==================================================================== - { - // CPI context for PDAs - set to first() since we have mints coming after - let cpi_context = CompressedCpiContext::first(); - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - - // 4a. Prepare Borsh PDA (index 0) - let borsh_record_key = *self.borsh_record.key(); - prepare_compressed_account_on_init( - &borsh_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 0, // assigned_account_index = 0 - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - // Set compression_info on the Borsh record via mut_from_account_data + const NUM_LIGHT_PDAS: usize = 2; + const NUM_LIGHT_MINTS: usize = 1; + const NUM_TOKENS: usize = 1; + const NUM_ATAS: usize = 1; + + let authority_key = *self.authority.key(); + let mint_signer_key = *self.mint_signer.key(); + let mint_key = *self.mint.key(); + + let mint_signer_seeds: &[&[u8]] = &[ + ALL_MINT_SIGNER_SEED, + authority_key.as_ref(), + &[params.mint_signer_bump], + ]; + + let vault_seeds: &[&[u8]] = &[ + ALL_TOKEN_VAULT_SEED, + mint_key.as_ref(), + &[params.token_vault_bump], + ]; + + // Capture references for the pda_setup closure + let borsh_record = self.borsh_record; + let zero_copy_record = self.zero_copy_record; + + create_accounts::( + [ + PdaInitParam { + account: self.borsh_record, + }, + PdaInitParam { + account: self.zero_copy_record, + }, + ], + |light_config, current_slot| { + // Set compression_info on the Borsh record { - let mut account_data = self - .borsh_record + let mut account_data = borsh_record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; let record = crate::pda::MinimalRecord::mut_from_account_data(&mut account_data); - record.set_decompressed(&light_config, current_slot); + record.set_decompressed(light_config, current_slot); } - - // 4b. Prepare ZeroCopy PDA (index 1) - let zero_copy_record_key = *self.zero_copy_record.key(); - prepare_compressed_account_on_init( - &zero_copy_record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - 1, // assigned_account_index = 1 - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; + // Set compression_info on the ZeroCopy record { - let mut account_data = self - .zero_copy_record + let mut account_data = zero_copy_record .try_borrow_mut_data() .map_err(|_| LightSdkTypesError::Borsh)?; let record_bytes = &mut account_data [8..8 + core::mem::size_of::()]; let record: &mut crate::account_loader::ZeroCopyRecord = bytemuck::from_bytes_mut(record_bytes); - record.set_decompressed(&light_config, current_slot); + record.set_decompressed(light_config, current_slot); } - - // 4c. Build instruction data and write to CPI context (doesn't execute yet) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - - // Write to CPI context first (combined execution happens with mints) - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - - // ==================================================================== - // 5. Create Mint via CreateMints with cpi_context_offset - // ==================================================================== - { - let authority_key = *self.authority.key(); - let mint_signer_key = *self.mint_signer.key(); - - let mint_signer_seeds: &[&[u8]] = &[ - ALL_MINT_SIGNER_SEED, - authority_key.as_ref(), - &[params.mint_signer_bump], - ]; - - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [SingleMintParams { + Ok(()) + }, + Some(CreateMintsInput { + params: [SingleMintParams { decimals: 6, mint_authority: authority_key, mint_bump: None, @@ -178,71 +93,35 @@ impl LightPreInit for CreateAllAccounts<'_> { authority_seeds: None, mint_signer_seeds: Some(mint_signer_seeds), token_metadata: None, - }]; - - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: self.mint_signers_slice, - mint_accounts: self.mints_slice, - static_accounts: CreateMintsStaticAccounts { - fee_payer: self.payer, - compressible_config: self.compressible_config, - rent_sponsor: self.rent_sponsor, - cpi_authority: self.cpi_authority, - }, - cpi_context_offset: NUM_LIGHT_PDAS as u8, - } - .invoke(&cpi_accounts)?; - } - - // ==================================================================== - // 6. Create Token Vault via CreateTokenAccountCpi - // ==================================================================== - { - let mint_key = *self.mint.key(); - let vault_seeds: &[&[u8]] = &[ - ALL_TOKEN_VAULT_SEED, - mint_key.as_ref(), - &[params.token_vault_bump], - ]; - - CreateTokenAccountCpi { - payer: self.payer, - account: self.token_vault, - mint: self.mint, - owner: *self.vault_owner.key(), - } - .rent_free( - self.compressible_config, - self.rent_sponsor, - self.system_program, - &crate::ID, - ) - .invoke_signed(vault_seeds)?; - } - - // ==================================================================== - // 7. Create ATA via CreateTokenAtaCpi - // ==================================================================== - { - CreateTokenAtaCpi { - payer: self.payer, - owner: self.ata_owner, - mint: self.mint, - ata: self.user_ata, - } - .rent_free( - self.compressible_config, - self.rent_sponsor, - self.system_program, - ) - .invoke()?; - } - - Ok(WITH_CPI_CONTEXT) - }; - inner() + }], + mint_seed_accounts: [self.mint_signers_slice[0]], + mint_accounts: [self.mints_slice[0]], + }), + [TokenInitParam { + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + seeds: vault_seeds, + }], + [AtaInitParam { + ata: self.user_ata, + owner: self.ata_owner, + mint: self.mint, + idempotent: false, + }], + &SharedAccounts { + fee_payer: self.payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(self.compression_config), + compressible_config: Some(self.compressible_config), + rent_sponsor: Some(self.rent_sponsor), + cpi_authority: Some(self.cpi_authority), + system_program: Some(self.system_program), + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/pinocchio-manual-test/src/pda/derived_accounts.rs b/sdk-tests/pinocchio-manual-test/src/pda/derived_accounts.rs index f5dbd70139..173e0f8999 100644 --- a/sdk-tests/pinocchio-manual-test/src/pda/derived_accounts.rs +++ b/sdk-tests/pinocchio-manual-test/src/pda/derived_accounts.rs @@ -1,12 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use light_account_pinocchio::{ + create_accounts, light_account_checks::{self, packed_accounts::ProgramPackedAccounts}, - prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig, CpiContextWriteAccounts, - InvokeLightSystemProgram, LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, - LightSdkTypesError, PackedAddressTreeInfoExt, PackedLightAccountVariantTrait, -}; -use light_compressed_account::instruction_data::{ - cpi_context::CompressedCpiContext, with_account_info::InstructionDataInvokeCpiWithAccountInfo, + LightAccount, LightAccountVariantTrait, LightFinalize, LightPreInit, LightSdkTypesError, + PackedLightAccountVariantTrait, PdaInitParam, SharedAccounts, }; use pinocchio::account_info::AccountInfo; @@ -38,117 +35,36 @@ impl LightPreInit for CreatePda<'_> { remaining_accounts: &[AccountInfo], params: &CreatePdaParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - use light_account_pinocchio::{LightAccount, LightConfig}; - use pinocchio::sysvars::{clock::Clock, Sysvar}; - - // 1. Build CPI accounts (slice remaining_accounts at system_accounts_offset) - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - self.fee_payer, - &remaining_accounts[system_accounts_offset..], - config, - ); - - // 2. Get address tree pubkey from packed tree info - let address_tree_info = ¶ms.create_accounts_proof.address_tree_info; - let address_tree_pubkey = address_tree_info - .get_tree_pubkey(&cpi_accounts) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let output_tree_index = params.create_accounts_proof.output_state_tree_index; - let current_account_index: u8 = 0; - // Is true if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - const WITH_CPI_CONTEXT: bool = false; - - const NUM_LIGHT_PDAS: usize = 1; - - // 6. Set compression_info from config - let light_config = LightConfig::load_checked(self.compression_config, &crate::ID) - .map_err(|_| LightSdkTypesError::InvalidInstructionData)?; - let current_slot = Clock::get() - .map_err(|_| LightSdkTypesError::InvalidInstructionData)? - .slot; - // Dynamic derived light pda specific. Only exists if NUM_LIGHT_PDAS > 0 - // ===================================================================== - { - // Is first if the instruction creates 1 or more light mints in addition to 1 or more light pda accounts. - let cpi_context = if WITH_CPI_CONTEXT { - CompressedCpiContext::first() - } else { - CompressedCpiContext::default() - }; - let mut new_address_params = Vec::with_capacity(NUM_LIGHT_PDAS); - let mut account_infos = Vec::with_capacity(NUM_LIGHT_PDAS); - // 3. Prepare compressed account using helper function - // Dynamic code 0-N variants depending on the accounts struct - // ===================================================================== - let record_key = *self.record.key(); - prepare_compressed_account_on_init( - &record_key, - &address_tree_pubkey, - address_tree_info, - output_tree_index, - current_account_index, - &crate::ID, - &mut new_address_params, - &mut account_infos, - )?; - // Set compression_info on the Borsh record via mut_from_account_data - { - let mut account_data = self - .record - .try_borrow_mut_data() - .map_err(|_| LightSdkTypesError::Borsh)?; - let record = MinimalRecord::mut_from_account_data(&mut account_data); - record.set_decompressed(&light_config, current_slot); - } - // ===================================================================== - - // current_account_index += 1; - // For multiple accounts, repeat the pattern: - // let prepared2 = prepare_compressed_account_on_init(..., current_account_index, ...)?; - // current_account_index += 1; - - // 4. Build instruction data manually (no builder pattern) - let instruction_data = InstructionDataInvokeCpiWithAccountInfo { - mode: 1, // V2 mode - bump: crate::LIGHT_CPI_SIGNER.bump, - invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(), - compress_or_decompress_lamports: 0, - is_compress: false, - with_cpi_context: WITH_CPI_CONTEXT, - with_transaction_hash: false, - cpi_context, - proof: params.create_accounts_proof.proof.0, - new_address_params, - account_infos, - read_only_addresses: vec![], - read_only_accounts: vec![], - }; - if !WITH_CPI_CONTEXT { - // 5. Invoke Light System Program CPI - instruction_data.invoke(cpi_accounts)?; - } else { - // For flows that combine light mints with light PDAs, write to CPI context first. - // The authority and cpi_context accounts must be provided in remaining_accounts. - let cpi_context_accounts = CpiContextWriteAccounts { - fee_payer: cpi_accounts.fee_payer(), - authority: cpi_accounts.authority()?, - cpi_context: cpi_accounts.cpi_context()?, - cpi_signer: crate::LIGHT_CPI_SIGNER, - }; - instruction_data.invoke_write_to_cpi_context_first(cpi_context_accounts)?; - } - } - // ===================================================================== - Ok(false) // No mints, so no CPI context write - }; - inner() + let record = self.record; + + create_accounts::( + [PdaInitParam { + account: self.record, + }], + |light_config, current_slot| { + let mut account_data = record + .try_borrow_mut_data() + .map_err(|_| LightSdkTypesError::Borsh)?; + let record = MinimalRecord::mut_from_account_data(&mut account_data); + record.set_decompressed(light_config, current_slot); + Ok(()) + }, + None, + [], + [], + &SharedAccounts { + fee_payer: self.fee_payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: Some(self.compression_config), + compressible_config: None, + rent_sponsor: None, + cpi_authority: None, + system_program: None, + }, + remaining_accounts, + ) } } diff --git a/sdk-tests/pinocchio-manual-test/src/token_account/derived.rs b/sdk-tests/pinocchio-manual-test/src/token_account/derived.rs index 90efc00eae..40c835e45e 100644 --- a/sdk-tests/pinocchio-manual-test/src/token_account/derived.rs +++ b/sdk-tests/pinocchio-manual-test/src/token_account/derived.rs @@ -21,32 +21,24 @@ impl LightPreInit for CreateTokenVaultAccou _remaining_accounts: &[AccountInfo], params: &CreateTokenVaultParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - // Build PDA seeds: [TOKEN_VAULT_SEED, mint.key(), &[bump]] - let mint_key = *self.mint.key(); - let vault_seeds: &[&[u8]] = - &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; + let mint_key = *self.mint.key(); + let vault_seeds: &[&[u8]] = &[TOKEN_VAULT_SEED, mint_key.as_ref(), &[params.vault_bump]]; - // Create token account via CPI with rent-free mode - // In pinocchio, accounts are already &AccountInfo, no .to_account_info() needed - CreateTokenAccountCpi { - payer: self.payer, - account: self.token_vault, - mint: self.mint, - owner: *self.vault_owner.key(), - } - .rent_free( - self.compressible_config, - self.rent_sponsor, - self.system_program, - &crate::ID, - ) - .invoke_signed(vault_seeds)?; + CreateTokenAccountCpi { + payer: self.payer, + account: self.token_vault, + mint: self.mint, + owner: *self.vault_owner.key(), + } + .rent_free( + self.compressible_config, + self.rent_sponsor, + self.system_program, + &crate::ID, + ) + .invoke_signed(vault_seeds)?; - // Token accounts don't use CPI context, return false - Ok(false) - }; - inner() + Ok(false) } } diff --git a/sdk-tests/pinocchio-manual-test/src/two_mints/derived.rs b/sdk-tests/pinocchio-manual-test/src/two_mints/derived.rs index e06bba669f..cb8f29914e 100644 --- a/sdk-tests/pinocchio-manual-test/src/two_mints/derived.rs +++ b/sdk-tests/pinocchio-manual-test/src/two_mints/derived.rs @@ -2,8 +2,8 @@ //! Contains LightPreInit/LightFinalize trait implementations. use light_account_pinocchio::{ - CpiAccounts, CpiAccountsConfig, CreateMints, CreateMintsStaticAccounts, LightFinalize, - LightPreInit, LightSdkTypesError, SingleMintParams, + create_accounts, CreateMintsInput, LightFinalize, LightPreInit, LightSdkTypesError, + SharedAccounts, SingleMintParams, }; use pinocchio::account_info::AccountInfo; @@ -21,84 +21,65 @@ impl LightPreInit for CreateDerivedMintsA remaining_accounts: &[AccountInfo], params: &CreateDerivedMintsParams, ) -> std::result::Result { - let inner = || -> std::result::Result { - // 1. Build CPI accounts - let system_accounts_offset = - params.create_accounts_proof.system_accounts_offset as usize; - if remaining_accounts.len() < system_accounts_offset { - return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts); - } - let config = CpiAccountsConfig::new_with_cpi_context(crate::LIGHT_CPI_SIGNER); - let cpi_accounts = CpiAccounts::new_with_config( - self.payer, - &remaining_accounts[system_accounts_offset..], - config, - ); + let authority = *self.authority.key(); + let mint_signer_0 = *self.mint_signer_0.key(); + let mint_signer_1 = *self.mint_signer_1.key(); - // Constants - const NUM_LIGHT_MINTS: usize = 2; - const NUM_LIGHT_PDAS: usize = 0; - #[allow(clippy::absurd_extreme_comparisons)] - const WITH_CPI_CONTEXT: bool = NUM_LIGHT_PDAS > 0 && NUM_LIGHT_MINTS > 0; + let mint_signer_0_seeds: &[&[u8]] = &[ + MINT_SIGNER_0_SEED, + authority.as_ref(), + &[params.mint_signer_0_bump], + ]; + let mint_signer_1_seeds: &[&[u8]] = &[ + MINT_SIGNER_1_SEED, + authority.as_ref(), + &[params.mint_signer_1_bump], + ]; - // 2. Build mint params - let authority = *self.authority.key(); - let mint_signer_0 = *self.mint_signer_0.key(); - let mint_signer_1 = *self.mint_signer_1.key(); - - let mint_signer_0_seeds: &[&[u8]] = &[ - MINT_SIGNER_0_SEED, - authority.as_ref(), - &[params.mint_signer_0_bump], - ]; - let mint_signer_1_seeds: &[&[u8]] = &[ - MINT_SIGNER_1_SEED, - authority.as_ref(), - &[params.mint_signer_1_bump], - ]; - - let sdk_mints: [SingleMintParams<'_>; NUM_LIGHT_MINTS] = [ - SingleMintParams { - decimals: 6, - mint_authority: authority, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_0, - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_0_seeds), - token_metadata: None, - }, - SingleMintParams { - decimals: 9, - mint_authority: authority, - mint_bump: None, - freeze_authority: None, - mint_seed_pubkey: mint_signer_1, - authority_seeds: None, - mint_signer_seeds: Some(mint_signer_1_seeds), - token_metadata: None, - }, - ]; - - // 3. Create mints - CreateMints { - mints: &sdk_mints, - proof_data: ¶ms.create_accounts_proof, - mint_seed_accounts: self.mint_signers_slice, - mint_accounts: self.mints_slice, - static_accounts: CreateMintsStaticAccounts { - fee_payer: self.payer, - compressible_config: self.compressible_config, - rent_sponsor: self.rent_sponsor, - cpi_authority: self.cpi_authority, - }, - cpi_context_offset: NUM_LIGHT_PDAS as u8, - } - .invoke(&cpi_accounts)?; - - Ok(WITH_CPI_CONTEXT) - }; - inner() + create_accounts::( + [], + |_, _| Ok(()), + Some(CreateMintsInput { + params: [ + SingleMintParams { + decimals: 6, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_0, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_0_seeds), + token_metadata: None, + }, + SingleMintParams { + decimals: 9, + mint_authority: authority, + mint_bump: None, + freeze_authority: None, + mint_seed_pubkey: mint_signer_1, + authority_seeds: None, + mint_signer_seeds: Some(mint_signer_1_seeds), + token_metadata: None, + }, + ], + mint_seed_accounts: [self.mint_signers_slice[0], self.mint_signers_slice[1]], + mint_accounts: [self.mints_slice[0], self.mints_slice[1]], + }), + [], + [], + &SharedAccounts { + fee_payer: self.payer, + cpi_signer: crate::LIGHT_CPI_SIGNER, + proof: ¶ms.create_accounts_proof, + program_id: crate::ID, + compression_config: None, + compressible_config: Some(self.compressible_config), + rent_sponsor: Some(self.rent_sponsor), + cpi_authority: Some(self.cpi_authority), + system_program: None, + }, + remaining_accounts, + ) } }