diff --git a/sdk-libs/macros/src/light_pdas/accounts/variant.rs b/sdk-libs/macros/src/light_pdas/accounts/variant.rs index 1a8ae3fa9e..accf1f38c2 100644 --- a/sdk-libs/macros/src/light_pdas/accounts/variant.rs +++ b/sdk-libs/macros/src/light_pdas/accounts/variant.rs @@ -16,6 +16,7 @@ use quote::{format_ident, quote}; use syn::{Ident, Type}; use crate::light_pdas::{ + backend::{AnchorBackend, CodegenBackend, PinocchioBackend}, seeds::{ClassifiedSeed, FnArgKind}, shared_utils::make_packed_type, }; @@ -86,23 +87,7 @@ impl VariantBuilder { /// Generate all variant code for this PDA field. pub fn build(&self) -> TokenStream { - let seeds_struct = self.generate_seeds_struct(); - let packed_seeds_struct = self.generate_packed_seeds_struct(); - let variant_struct = self.generate_variant_struct(); - let packed_variant_struct = self.generate_packed_variant_struct(); - let light_account_variant_impl = self.generate_light_account_variant_impl(); - let packed_light_account_variant_impl = self.generate_packed_light_account_variant_impl(); - let pack_impl = self.generate_pack_impl(); - - quote! { - #seeds_struct - #packed_seeds_struct - #variant_struct - #packed_variant_struct - #light_account_variant_impl - #packed_light_account_variant_impl - #pack_impl - } + self.build_with_backend(&AnchorBackend) } /// Generate all variant code for this PDA field (pinocchio version). @@ -113,14 +98,23 @@ impl VariantBuilder { /// - `[u8; 32]` instead of `Pubkey` for seed fields /// - `pinocchio::account_info::AccountInfo` for AccountInfo references pub fn build_for_pinocchio(&self) -> TokenStream { - let seeds_struct = self.generate_seeds_struct_pinocchio(); - let packed_seeds_struct = self.generate_packed_seeds_struct_pinocchio(); - let variant_struct = self.generate_variant_struct_pinocchio(); - let packed_variant_struct = self.generate_packed_variant_struct_pinocchio(); - let light_account_variant_impl = self.generate_light_account_variant_impl_pinocchio(); + self.build_with_backend(&PinocchioBackend) + } + + /// Generate all variant code using the specified backend. + /// + /// This is the unified implementation that both `build()` and `build_for_pinocchio()` + /// delegate to. + pub fn build_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { + let seeds_struct = self.generate_seeds_struct_with_backend(backend); + let packed_seeds_struct = self.generate_packed_seeds_struct_with_backend(backend); + let variant_struct = self.generate_variant_struct_with_backend(backend); + let packed_variant_struct = self.generate_packed_variant_struct_with_backend(backend); + let light_account_variant_impl = + self.generate_light_account_variant_impl_with_backend(backend); let packed_light_account_variant_impl = - self.generate_packed_light_account_variant_impl_pinocchio(); - let pack_impl = self.generate_pack_impl_pinocchio(); + self.generate_packed_light_account_variant_impl_with_backend(backend); + let pack_impl = self.generate_pack_impl_with_backend(backend); quote! { #seeds_struct @@ -134,35 +128,54 @@ impl VariantBuilder { } // ========================================================================= - // PINOCCHIO GENERATION METHODS + // UNIFIED BACKEND-BASED GENERATION METHODS // ========================================================================= - fn generate_seeds_struct_pinocchio(&self) -> TokenStream { + /// Generate the `{Field}Seeds` struct using the specified backend. + fn generate_seeds_struct_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { let struct_name = format_ident!("{}Seeds", self.variant_name); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let fields: Vec<_> = self .seed_fields .iter() .map(|sf| { let name = &sf.field_name; - let ty = if sf.is_account_seed { - quote! { [u8; 32] } - } else if sf.has_le_bytes { - quote! { u64 } + let ty = if backend.is_pinocchio() { + // Pinocchio uses [u8; 32] for all pubkey fields + if sf.is_account_seed { + quote! { [u8; 32] } + } else if sf.has_le_bytes { + quote! { u64 } + } else { + quote! { [u8; 32] } + } } else { - quote! { [u8; 32] } + // Anchor uses the original field type (Pubkey, u64, etc.) + sf.field_type.clone() }; quote! { pub #name: #ty } }) .collect(); + let doc_attr = if backend.is_pinocchio() { + quote! {} + } else { + let doc = format!("Seeds for {} PDA.", self.variant_name); + quote! { #[doc = #doc] } + }; + if fields.is_empty() { quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + #doc_attr + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #struct_name; } } else { quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + #doc_attr + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #struct_name { #(#fields,)* } @@ -170,8 +183,15 @@ impl VariantBuilder { } } - fn generate_packed_seeds_struct_pinocchio(&self) -> TokenStream { + /// Generate the `Packed{Field}Seeds` struct using the specified backend. + fn generate_packed_seeds_struct_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { let struct_name = format_ident!("Packed{}Seeds", self.variant_name); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let fields: Vec<_> = self .seed_fields .iter() @@ -186,8 +206,19 @@ impl VariantBuilder { }) .collect(); + let doc_attr = if backend.is_pinocchio() { + quote! {} + } else { + let doc = format!( + "Packed seeds with u8 indices for {} PDA.", + self.variant_name + ); + quote! { #[doc = #doc] } + }; + quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + #doc_attr + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #struct_name { #(#fields,)* pub bump: u8, @@ -195,13 +226,27 @@ impl VariantBuilder { } } - fn generate_variant_struct_pinocchio(&self) -> TokenStream { + /// Generate the `{Field}Variant` struct using the specified backend. + fn generate_variant_struct_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { let struct_name = format_ident!("{}Variant", self.variant_name); let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); let inner_type = &self.inner_type; + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + + let doc_attr = if backend.is_pinocchio() { + quote! {} + } else { + let doc = format!( + "Full variant combining seeds + data for {}.", + self.variant_name + ); + quote! { #[doc = #doc] } + }; quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + #doc_attr + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #struct_name { pub seeds: #seeds_struct_name, pub data: #inner_type, @@ -209,10 +254,17 @@ impl VariantBuilder { } } - fn generate_packed_variant_struct_pinocchio(&self) -> TokenStream { + /// Generate the `Packed{Field}Variant` struct using the specified backend. + fn generate_packed_variant_struct_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { let struct_name = format_ident!("Packed{}Variant", self.variant_name); let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); let inner_type = &self.inner_type; + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let data_type = if let Some(packed_type) = make_packed_type(inner_type) { quote! { #packed_type } } else { @@ -221,8 +273,19 @@ impl VariantBuilder { quote! { #packed_name } }; + let doc_attr = if backend.is_pinocchio() { + quote! {} + } else { + let doc = format!( + "Packed variant for efficient serialization of {}.", + self.variant_name + ); + quote! { #[doc = #doc] } + }; + quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] + #doc_attr + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #struct_name { pub seeds: #packed_seeds_struct_name, pub data: #data_type, @@ -230,18 +293,23 @@ impl VariantBuilder { } } - fn generate_light_account_variant_impl_pinocchio(&self) -> TokenStream { + /// Generate `impl LightAccountVariantTrait` using the specified backend. + fn generate_light_account_variant_impl_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { let variant_name = format_ident!("{}Variant", self.variant_name); let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); let inner_type = &self.inner_type; let seed_count = self.seed_count; + let account_crate = backend.account_crate(); - let seed_vec_items = self.generate_seed_vec_items_pinocchio(); + let seed_vec_items = self.generate_seed_vec_items_with_backend(backend); let seed_refs_items = self.generate_seed_refs_items(); quote! { - impl light_account_pinocchio::LightAccountVariantTrait<#seed_count> for #variant_name { + impl #account_crate::LightAccountVariantTrait<#seed_count> for #variant_name { const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; type Seeds = #seeds_struct_name; @@ -263,37 +331,44 @@ impl VariantBuilder { } } - fn generate_packed_light_account_variant_impl_pinocchio(&self) -> TokenStream { + /// Generate `impl PackedLightAccountVariantTrait` using the specified backend. + fn generate_packed_light_account_variant_impl_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { let variant_name = format_ident!("{}Variant", self.variant_name); let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); let inner_type = &self.inner_type; let seed_count = self.seed_count; + let account_crate = backend.account_crate(); + let account_info_trait = backend.account_info_trait(); + let sdk_error = backend.sdk_error_type(); - let unpack_seed_stmts = self.generate_unpack_seed_statements_pinocchio(); - let unpack_seed_fields = self.generate_unpack_seed_fields_pinocchio(); - let packed_seed_refs_items = self.generate_packed_seed_refs_items_pinocchio(); + let unpack_seed_stmts = self.generate_unpack_seed_statements_with_backend(backend); + let unpack_seed_fields = self.generate_unpack_seed_fields(); + let packed_seed_refs_items = self.generate_packed_seed_refs_items_with_backend(backend); let unpack_data = quote! { { - let packed_accounts = light_account_pinocchio::light_account_checks::packed_accounts::ProgramPackedAccounts { accounts }; - <#inner_type as light_account_pinocchio::LightAccount>::unpack(&self.data, &packed_accounts) - .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + let packed_accounts = #account_crate::packed_accounts::ProgramPackedAccounts { accounts }; + <#inner_type as #account_crate::LightAccount>::unpack(&self.data, &packed_accounts) + .map_err(|_| #sdk_error::InvalidInstructionData)? } }; quote! { - impl light_account_pinocchio::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { + impl #account_crate::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { type Unpacked = #variant_name; - const ACCOUNT_TYPE: light_account_pinocchio::AccountType = - <#inner_type as light_account_pinocchio::LightAccount>::ACCOUNT_TYPE; + const ACCOUNT_TYPE: #account_crate::AccountType = + <#inner_type as #account_crate::LightAccount>::ACCOUNT_TYPE; fn bump(&self) -> u8 { self.seeds.bump } - fn unpack(&self, accounts: &[AI]) -> std::result::Result { + fn unpack(&self, accounts: &[AI]) -> std::result::Result { #(#unpack_seed_stmts)* Ok(#variant_name { @@ -304,55 +379,86 @@ impl VariantBuilder { }) } - fn seed_refs_with_bump<'a, AI: light_account_pinocchio::light_account_checks::AccountInfoTrait>( + fn seed_refs_with_bump<'a, AI: #account_info_trait>( &'a self, accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], light_account_pinocchio::LightSdkTypesError> { + ) -> std::result::Result<[&'a [u8]; #seed_count], #sdk_error> { Ok([#(#packed_seed_refs_items,)* bump_storage]) } } } } - fn generate_pack_impl_pinocchio(&self) -> TokenStream { + /// Generate `impl Pack` for the variant struct using the specified backend. + fn generate_pack_impl_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { let variant_name = format_ident!("{}Variant", self.variant_name); let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); let inner_type = &self.inner_type; + let account_crate = backend.account_crate(); + let sdk_error = backend.sdk_error_type(); - let pack_seed_fields = self.generate_pack_seed_fields_pinocchio(); + let pack_seed_fields = self.generate_pack_seed_fields_with_backend(backend); let pack_data = quote! { - <#inner_type as light_account_pinocchio::LightAccount>::pack(&self.data, accounts) - .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + <#inner_type as #account_crate::LightAccount>::pack(&self.data, accounts) + .map_err(|_| #sdk_error::InvalidInstructionData)? }; - quote! { - #[cfg(not(target_os = "solana"))] - impl light_account_pinocchio::Pack for #variant_name { - type Packed = #packed_variant_name; - - fn pack( - &self, - accounts: &mut light_account_pinocchio::PackedAccounts, - ) -> std::result::Result { - use light_account_pinocchio::LightAccountVariantTrait; - let (_, bump) = self.derive_pda::(); - Ok(#packed_variant_name { - seeds: #packed_seeds_struct_name { - #(#pack_seed_fields,)* - bump, - }, - data: #pack_data, - }) + if backend.is_pinocchio() { + quote! { + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack<#account_crate::solana_instruction::AccountMeta> for #variant_name { + type Packed = #packed_variant_name; + + fn pack( + &self, + accounts: &mut #account_crate::PackedAccounts, + ) -> std::result::Result { + use #account_crate::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::(); + Ok(#packed_variant_name { + seeds: #packed_seeds_struct_name { + #(#pack_seed_fields,)* + bump, + }, + data: #pack_data, + }) + } + } + } + } else { + quote! { + // Pack trait is only available off-chain (client-side packing) + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack for #variant_name { + type Packed = #packed_variant_name; + + fn pack( + &self, + accounts: &mut #account_crate::interface::instruction::PackedAccounts, + ) -> std::result::Result { + use #account_crate::LightAccountVariantTrait; + let (_, bump) = self.derive_pda::<#account_crate::AccountInfo<'static>>(); + Ok(#packed_variant_name { + seeds: #packed_seeds_struct_name { + #(#pack_seed_fields,)* + bump, + }, + data: #pack_data, + }) + } } } } } - /// Generate seed_vec items for pinocchio (uses `.to_vec()` on `[u8; 32]` instead of `.to_bytes().to_vec()`). - fn generate_seed_vec_items_pinocchio(&self) -> Vec { + /// Generate seed_vec items using the specified backend. + fn generate_seed_vec_items_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> Vec { self.seeds .iter() .map(|seed| match seed { @@ -363,14 +469,24 @@ impl VariantBuilder { quote! { (#expr).to_vec() } } ClassifiedSeed::CtxRooted { account, .. } => { - quote! { self.seeds.#account.to_vec() } + if backend.is_pinocchio() { + // Pinocchio: already [u8; 32], just .to_vec() + quote! { self.seeds.#account.to_vec() } + } else { + // Anchor: Pubkey needs .to_bytes().to_vec() + quote! { self.seeds.#account.to_bytes().to_vec() } + } } ClassifiedSeed::DataRooted { root, expr, .. } => { let field = extract_data_field_name(root, expr); if is_le_bytes_expr(expr) { quote! { self.seeds.#field.to_le_bytes().to_vec() } - } else { + } else if backend.is_pinocchio() { + // Pinocchio: already [u8; 32], just .to_vec() quote! { self.seeds.#field.to_vec() } + } else { + // Anchor: Pubkey needs .to_bytes().to_vec() + quote! { self.seeds.#field.to_bytes().to_vec() } } } ClassifiedSeed::FunctionCall { @@ -389,33 +505,53 @@ impl VariantBuilder { .collect() } - fn generate_unpack_seed_statements_pinocchio(&self) -> Vec { + /// Generate unpack seed statements using the specified backend. + fn generate_unpack_seed_statements_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> Vec { + let sdk_error = backend.sdk_error_type(); + self.seed_fields .iter() .filter(|sf| sf.is_account_seed) .map(|sf| { let field = &sf.field_name; let idx_field = format_ident!("{}_idx", field); - quote! { - let #field: [u8; 32] = - accounts - .get(self.seeds.#idx_field as usize) - .ok_or(light_account_pinocchio::LightSdkTypesError::NotEnoughAccountKeys)? - .key(); + if backend.is_pinocchio() { + quote! { + let #field: [u8; 32] = + accounts + .get(self.seeds.#idx_field as usize) + .ok_or(#sdk_error::NotEnoughAccountKeys)? + .key(); + } + } else { + quote! { + let #field = solana_pubkey::Pubkey::new_from_array( + accounts + .get(self.seeds.#idx_field as usize) + .ok_or(#sdk_error::NotEnoughAccountKeys)? + .key() + ); + } } }) .collect() } - fn generate_unpack_seed_fields_pinocchio(&self) -> Vec { + /// Generate unpack seed field assignments. + fn generate_unpack_seed_fields(&self) -> Vec { self.seed_fields .iter() .map(|sf| { let field = &sf.field_name; if sf.is_account_seed { + // For account seeds, we bind to a local variable in unpack_seed_statements quote! { #field } } else if sf.has_le_bytes { - quote! { #field: u64::from_le_bytes(self.seeds.#field) } + let ty = &sf.field_type; + quote! { #field: #ty::from_le_bytes(self.seeds.#field) } } else { quote! { #field: self.seeds.#field } } @@ -423,7 +559,13 @@ impl VariantBuilder { .collect() } - fn generate_packed_seed_refs_items_pinocchio(&self) -> Vec { + /// Generate packed seed refs items using the specified backend. + fn generate_packed_seed_refs_items_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> Vec { + let sdk_error = backend.sdk_error_type(); + self.seeds .iter() .map(|seed| match seed { @@ -450,7 +592,7 @@ impl VariantBuilder { quote! { accounts .get(self.seeds.#idx_field as usize) - .ok_or(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? + .ok_or(#sdk_error::InvalidInstructionData)? .key_ref() } } @@ -475,14 +617,22 @@ impl VariantBuilder { .collect() } - fn generate_pack_seed_fields_pinocchio(&self) -> Vec { + /// Generate pack seed fields using the specified backend. + fn generate_pack_seed_fields_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> Vec { self.seed_fields .iter() .map(|sf| { let field = &sf.field_name; if sf.is_account_seed { let idx_field = format_ident!("{}_idx", field); - quote! { #idx_field: accounts.insert_or_get(light_account_pinocchio::solana_pubkey::Pubkey::from(self.seeds.#field)) } + if backend.is_pinocchio() { + quote! { #idx_field: accounts.insert_or_get(light_account_pinocchio::solana_pubkey::Pubkey::from(self.seeds.#field)) } + } else { + quote! { #idx_field: accounts.insert_or_get(AM::pubkey_from_bytes(self.seeds.#field.to_bytes())) } + } } else if sf.has_le_bytes { quote! { #field: self.seeds.#field.to_le_bytes() } } else { @@ -493,309 +643,11 @@ impl VariantBuilder { } // ========================================================================= - // ORIGINAL (ANCHOR) GENERATION METHODS + // SHARED HELPER METHODS (used by _with_backend methods) // ========================================================================= - /// Generate the `{Field}Seeds` struct. - fn generate_seeds_struct(&self) -> TokenStream { - let struct_name = format_ident!("{}Seeds", self.variant_name); - let doc = format!("Seeds for {} PDA.", self.variant_name); - - // Filter to only account and data seeds (constants are inline) - let fields: Vec<_> = self - .seed_fields - .iter() - .map(|sf| { - let name = &sf.field_name; - let ty = &sf.field_type; - quote! { pub #name: #ty } - }) - .collect(); - - // AnchorSerialize derive provides IdlBuild impl when idl-build feature is enabled - if fields.is_empty() { - quote! { - #[doc = #doc] - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub struct #struct_name; - } - } else { - quote! { - #[doc = #doc] - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub struct #struct_name { - #(#fields,)* - } - } - } - } - - /// Generate the `Packed{Field}Seeds` struct. - fn generate_packed_seeds_struct(&self) -> TokenStream { - let struct_name = format_ident!("Packed{}Seeds", self.variant_name); - let doc = format!( - "Packed seeds with u8 indices for {} PDA.", - self.variant_name - ); - - // Generate packed fields - let fields: Vec<_> = self - .seed_fields - .iter() - .map(|sf| { - let name = if sf.is_account_seed { - format_ident!("{}_idx", sf.field_name) - } else { - sf.field_name.clone() - }; - let ty = &sf.packed_field_type; - quote! { pub #name: #ty } - }) - .collect(); - - quote! { - #[doc = #doc] - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub struct #struct_name { - #(#fields,)* - pub bump: u8, - } - } - } - - /// Generate the `{Field}Variant` struct. - fn generate_variant_struct(&self) -> TokenStream { - let struct_name = format_ident!("{}Variant", self.variant_name); - let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); - let inner_type = &self.inner_type; - let doc = format!( - "Full variant combining seeds + data for {}.", - self.variant_name - ); - - quote! { - #[doc = #doc] - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub struct #struct_name { - pub seeds: #seeds_struct_name, - pub data: #inner_type, - } - } - } - - /// Generate the `Packed{Field}Variant` struct. - fn generate_packed_variant_struct(&self) -> TokenStream { - let struct_name = format_ident!("Packed{}Variant", self.variant_name); - let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); - let inner_type = &self.inner_type; - let doc = format!( - "Packed variant for efficient serialization of {}.", - self.variant_name - ); - - // Use packed data type for all accounts (including zero-copy) - // Zero-copy accounts use the same LightAccount::Packed pattern as regular accounts - let data_type = if let Some(packed_type) = make_packed_type(inner_type) { - quote! { #packed_type } - } else { - // Fallback: prepend "Packed" to the type name - let type_str = quote!(#inner_type).to_string().replace(' ', ""); - let packed_name = format_ident!("Packed{}", type_str); - quote! { #packed_name } - }; - - quote! { - #[doc = #doc] - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub struct #struct_name { - pub seeds: #packed_seeds_struct_name, - pub data: #data_type, - } - } - } - - /// Generate `impl LightAccountVariant` for the variant struct. - fn generate_light_account_variant_impl(&self) -> TokenStream { - let variant_name = format_ident!("{}Variant", self.variant_name); - let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); - let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); - let inner_type = &self.inner_type; - let seed_count = self.seed_count; - - // Generate seed_vec body - let seed_vec_items = self.generate_seed_vec_items(); - - // Generate seed_refs_with_bump body - let seed_refs_items = self.generate_seed_refs_items(); - - // NOTE: pack() is NOT generated here - it's in the Pack trait impl (off-chain only) - - quote! { - impl light_account::LightAccountVariantTrait<#seed_count> for #variant_name { - const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; - - type Seeds = #seeds_struct_name; - type Data = #inner_type; - type Packed = #packed_variant_name; - - fn data(&self) -> &Self::Data { - &self.data - } - - fn seed_vec(&self) -> Vec> { - vec![#(#seed_vec_items),*] - } - - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; #seed_count] { - [#(#seed_refs_items,)* bump_storage] - } - } - } - } - - /// Generate `impl PackedLightAccountVariant` for the packed variant struct. - fn generate_packed_light_account_variant_impl(&self) -> TokenStream { - let variant_name = format_ident!("{}Variant", self.variant_name); - let seeds_struct_name = format_ident!("{}Seeds", self.variant_name); - let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); - let inner_type = &self.inner_type; - let seed_count = self.seed_count; - - // Generate unpack seed fields - let unpack_seed_stmts = self.generate_unpack_seed_statements(false); - let unpack_seed_fields = self.generate_unpack_seed_fields(); - - // Generate seed_refs_with_bump body for packed variant - let packed_seed_refs_items = self.generate_packed_seed_refs_items(); - - // Use LightAccount::unpack for all accounts (including zero-copy) - // Build ProgramPackedAccounts from the accounts slice - let unpack_data = quote! { - { - let packed_accounts = light_account::packed_accounts::ProgramPackedAccounts { accounts }; - <#inner_type as light_account::LightAccount>::unpack(&self.data, &packed_accounts) - .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)? - } - }; - - quote! { - impl light_account::PackedLightAccountVariantTrait<#seed_count> for #packed_variant_name { - type Unpacked = #variant_name; - - const ACCOUNT_TYPE: light_account::AccountType = - <#inner_type as light_account::LightAccount>::ACCOUNT_TYPE; - - fn bump(&self) -> u8 { - self.seeds.bump - } - - fn unpack(&self, accounts: &[AI]) -> std::result::Result { - #(#unpack_seed_stmts)* - - Ok(#variant_name { - seeds: #seeds_struct_name { - #(#unpack_seed_fields,)* - }, - data: #unpack_data, - }) - } - - fn seed_refs_with_bump<'a, AI: light_account::AccountInfoTrait>( - &'a self, - accounts: &'a [AI], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], light_account::LightSdkTypesError> { - Ok([#(#packed_seed_refs_items,)* bump_storage]) - } - - // into_in_token_data and into_in_tlv use default impls from trait - // (return Err/None for PDA variants) - } - } - } - - /// Generate `impl Pack` for the variant struct. - /// - /// This is off-chain only (client-side packing). Gated with `#[cfg(not(target_os = "solana"))]`. - fn generate_pack_impl(&self) -> TokenStream { - let variant_name = format_ident!("{}Variant", self.variant_name); - let packed_variant_name = format_ident!("Packed{}Variant", self.variant_name); - let packed_seeds_struct_name = format_ident!("Packed{}Seeds", self.variant_name); - let inner_type = &self.inner_type; - - // Generate pack body for seed fields - let pack_seed_fields = self.generate_pack_seed_fields(); - - // Use LightAccount::pack for all accounts (including zero-copy) - let pack_data = quote! { - <#inner_type as light_account::LightAccount>::pack(&self.data, accounts) - .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)? - }; - - quote! { - // Pack trait is only available off-chain (client-side packing) - #[cfg(not(target_os = "solana"))] - impl light_account::Pack for #variant_name { - type Packed = #packed_variant_name; - - fn pack( - &self, - accounts: &mut light_account::interface::instruction::PackedAccounts, - ) -> std::result::Result { - use light_account::LightAccountVariantTrait; - let (_, bump) = self.derive_pda::>(); - Ok(#packed_variant_name { - seeds: #packed_seeds_struct_name { - #(#pack_seed_fields,)* - bump, - }, - data: #pack_data, - }) - } - } - } - } - - /// Generate seed_vec items for each seed. - fn generate_seed_vec_items(&self) -> Vec { - self.seeds - .iter() - .map(|seed| match seed { - ClassifiedSeed::Literal(_) - | ClassifiedSeed::Constant { .. } - | ClassifiedSeed::Passthrough(_) => { - let expr = seed_to_expr(seed, self.module_path.as_deref()); - quote! { (#expr).to_vec() } - } - ClassifiedSeed::CtxRooted { account, .. } => { - quote! { self.seeds.#account.to_bytes().to_vec() } - } - ClassifiedSeed::DataRooted { root, expr, .. } => { - let field = extract_data_field_name(root, expr); - if is_le_bytes_expr(expr) { - quote! { self.seeds.#field.to_le_bytes().to_vec() } - } else { - quote! { self.seeds.#field.to_bytes().to_vec() } - } - } - ClassifiedSeed::FunctionCall { - func_expr, - args, - has_as_ref, - } => { - // Reconstruct call with self.seeds.X args - let rewritten = rewrite_fn_call_for_self(func_expr, args); - if *has_as_ref { - quote! { #rewritten.as_ref().to_vec() } - } else { - quote! { (#rewritten).to_vec() } - } - } - }) - .collect() - } - /// Generate seed_refs_with_bump items for unpacked variant. + /// This is shared between Anchor and Pinocchio since it generates the same code. fn generate_seed_refs_items(&self) -> Vec { self.seeds .iter() @@ -859,132 +711,6 @@ impl VariantBuilder { }) .collect() } - - /// Generate pack statements for seed fields. - fn generate_pack_seed_fields(&self) -> Vec { - self.seed_fields - .iter() - .map(|sf| { - let field = &sf.field_name; - if sf.is_account_seed { - let idx_field = format_ident!("{}_idx", field); - quote! { #idx_field: accounts.insert_or_get(AM::pubkey_from_bytes(self.seeds.#field.to_bytes())) } - } else if sf.has_le_bytes { - quote! { #field: self.seeds.#field.to_le_bytes() } - } else { - quote! { #field: self.seeds.#field } - } - }) - .collect() - } - - /// Generate unpack statements to resolve indices to Pubkeys. - /// - /// Used in `unpack()` which returns `Result<..., LightSdkTypesError>`. - fn generate_unpack_seed_statements(&self, _for_program_error: bool) -> Vec { - self.seed_fields - .iter() - .filter(|sf| sf.is_account_seed) - .map(|sf| { - let field = &sf.field_name; - let idx_field = format_ident!("{}_idx", field); - quote! { - let #field = solana_pubkey::Pubkey::new_from_array( - accounts - .get(self.seeds.#idx_field as usize) - .ok_or(light_account::LightSdkTypesError::NotEnoughAccountKeys)? - .key() - ); - } - }) - .collect() - } - - /// Generate unpack seed field assignments. - fn generate_unpack_seed_fields(&self) -> Vec { - self.seed_fields - .iter() - .map(|sf| { - let field = &sf.field_name; - if sf.is_account_seed { - quote! { #field } - } else if sf.has_le_bytes { - let ty = &sf.field_type; - quote! { #field: #ty::from_le_bytes(self.seeds.#field) } - } else { - quote! { #field: self.seeds.#field } - } - }) - .collect() - } - - /// Generate seed_refs_with_bump items for packed variant. - /// - /// For packed variant, account seeds are looked up directly from the accounts slice - /// using inline expressions (borrowing from `accounts` with lifetime `'a`). - /// Data seeds are stored directly in the packed struct (borrowing from `&'a self`). - /// - /// Account lookups are inlined rather than bound to local variables to avoid - /// E0515 (cannot return value referencing local variable). - fn generate_packed_seed_refs_items(&self) -> Vec { - self.seeds - .iter() - .map(|seed| match seed { - ClassifiedSeed::Literal(_) | ClassifiedSeed::Constant { .. } => { - let expr = seed_to_expr(seed, self.module_path.as_deref()); - quote! { #expr } - } - ClassifiedSeed::Passthrough(pass_expr) => { - if expr_contains_call(pass_expr) { - // Use a typed block to avoid `!` type causing unreachable expression warnings. - quote! { - { - panic!("seed_refs_with_bump not supported for function call seeds on packed variant. \ - Use derive_pda() + seed_vec() instead."); - #[allow(unreachable_code)] - { bump_storage as &[u8] } - } - } - } else { - let expr = seed_to_expr(seed, self.module_path.as_deref()); - quote! { #expr } - } - } - ClassifiedSeed::CtxRooted { account, .. } => { - // Inline account lookup to borrow from `accounts` (lifetime 'a) - let idx_field = format_ident!("{}_idx", account); - quote! { - accounts - .get(self.seeds.#idx_field as usize) - .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? - .key_ref() - } - } - ClassifiedSeed::DataRooted { root, expr, .. } => { - let field = extract_data_field_name(root, expr); - if is_le_bytes_expr(expr) { - quote! { &self.seeds.#field } - } else { - quote! { self.seeds.#field.as_ref() } - } - } - ClassifiedSeed::FunctionCall { .. } => { - // FunctionCall args are packed as individual fields (account = idx, data = Pubkey) - // The packed_seed_refs_items needs the full reconstructed seed, but that's - // impossible without temporary allocations. - // Use a typed block to avoid `!` type causing unreachable expression warnings. - quote! { - { - panic!("seed_refs_with_bump not supported for function call seeds on packed variant. \ - Use derive_pda() + seed_vec() instead."); - #[allow(unreachable_code)] - { bump_storage as &[u8] } - } - } - } - }) - .collect() - } } /// Extract seed field information from classified seeds. diff --git a/sdk-libs/macros/src/light_pdas/backend.rs b/sdk-libs/macros/src/light_pdas/backend.rs new file mode 100644 index 0000000000..3e02b680f9 --- /dev/null +++ b/sdk-libs/macros/src/light_pdas/backend.rs @@ -0,0 +1,209 @@ +//! Code generation backend abstraction. +//! +//! This module provides a trait-based abstraction for generating code that differs +//! between Anchor and Pinocchio frameworks. Instead of duplicating methods with +//! `_pinocchio` suffixes, code generators accept a `CodegenBackend` implementation +//! that provides framework-specific types, paths, and conversions. +//! +//! # Example +//! +//! ```ignore +//! // Instead of: +//! fn generate_foo(&self) -> TokenStream { ... } +//! fn generate_foo_pinocchio(&self) -> TokenStream { ... } +//! +//! // Use: +//! fn generate_foo_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { ... } +//! ``` + +use proc_macro2::TokenStream; +use quote::quote; + +/// Trait for code generation backends (Anchor vs Pinocchio). +/// +/// This trait encapsulates all differences between Anchor and Pinocchio code generation: +/// - Serialization derives (Anchor vs Borsh) +/// - Crate paths (light_account vs light_account_pinocchio) +/// - Type representations (Pubkey vs [u8; 32]) +/// - Account info types +pub trait CodegenBackend { + // ------------------------------------------------------------------------- + // Serialization Derives + // ------------------------------------------------------------------------- + + /// Returns the serialize derive attribute (e.g., `anchor_lang::AnchorSerialize` or `borsh::BorshSerialize`). + fn serialize_derive(&self) -> TokenStream; + + /// Returns the deserialize derive attribute (e.g., `anchor_lang::AnchorDeserialize` or `borsh::BorshDeserialize`). + fn deserialize_derive(&self) -> TokenStream; + + // ------------------------------------------------------------------------- + // Crate Paths + // ------------------------------------------------------------------------- + + /// Returns the account crate path (`light_account` or `light_account_pinocchio`). + fn account_crate(&self) -> TokenStream; + + /// Returns the account info trait path. + fn account_info_trait(&self) -> TokenStream; + + // ------------------------------------------------------------------------- + // Types + // ------------------------------------------------------------------------- + + /// Returns the account info type for function signatures. + fn account_info_type(&self) -> TokenStream; + + /// Returns the packed accounts type for Pack trait implementations. + fn packed_accounts_type(&self) -> TokenStream; + + /// Returns the account meta type for Pack trait bounds. + fn account_meta_type(&self) -> TokenStream; + + // ------------------------------------------------------------------------- + // Flags + // ------------------------------------------------------------------------- + + /// Returns true if this is the Pinocchio backend. + fn is_pinocchio(&self) -> bool; + + // ------------------------------------------------------------------------- + // Error Handling + // ------------------------------------------------------------------------- + + /// Returns the error type for SDK errors. + fn sdk_error_type(&self) -> TokenStream; + + /// Returns the program error type. + fn program_error_type(&self) -> TokenStream; + + /// Returns the borrow error conversion for account data borrowing. + fn borrow_error(&self) -> TokenStream; +} + +/// Anchor backend implementation. +/// +/// Uses: +/// - `anchor_lang::AnchorSerialize/AnchorDeserialize` for serialization +/// - `light_account::` crate paths +/// - `Pubkey` type for public keys +/// - `anchor_lang::prelude::AccountInfo` for account info +pub struct AnchorBackend; + +impl CodegenBackend for AnchorBackend { + fn serialize_derive(&self) -> TokenStream { + quote! { anchor_lang::AnchorSerialize } + } + + fn deserialize_derive(&self) -> TokenStream { + quote! { anchor_lang::AnchorDeserialize } + } + + fn account_crate(&self) -> TokenStream { + quote! { light_account } + } + + fn account_info_trait(&self) -> TokenStream { + quote! { light_account::AccountInfoTrait } + } + + fn account_info_type(&self) -> TokenStream { + quote! { anchor_lang::prelude::AccountInfo } + } + + fn packed_accounts_type(&self) -> TokenStream { + quote! { light_account::interface::instruction::PackedAccounts } + } + + fn account_meta_type(&self) -> TokenStream { + quote! { light_account::AccountMetaTrait } + } + + fn is_pinocchio(&self) -> bool { + false + } + + fn sdk_error_type(&self) -> TokenStream { + quote! { light_account::LightSdkTypesError } + } + + fn program_error_type(&self) -> TokenStream { + quote! { anchor_lang::error::Error } + } + + fn borrow_error(&self) -> TokenStream { + quote! { ? } + } +} + +/// Pinocchio backend implementation. +/// +/// Uses: +/// - `borsh::BorshSerialize/BorshDeserialize` for serialization +/// - `light_account_pinocchio::` crate paths +/// - `[u8; 32]` type for public keys +/// - `pinocchio::account_info::AccountInfo` for account info +pub struct PinocchioBackend; + +impl CodegenBackend for PinocchioBackend { + fn serialize_derive(&self) -> TokenStream { + quote! { borsh::BorshSerialize } + } + + fn deserialize_derive(&self) -> TokenStream { + quote! { borsh::BorshDeserialize } + } + + fn account_crate(&self) -> TokenStream { + quote! { light_account_pinocchio } + } + + fn account_info_trait(&self) -> TokenStream { + quote! { light_account_pinocchio::light_account_checks::AccountInfoTrait } + } + + fn account_info_type(&self) -> TokenStream { + quote! { pinocchio::account_info::AccountInfo } + } + + fn packed_accounts_type(&self) -> TokenStream { + quote! { light_account_pinocchio::PackedAccounts } + } + + fn account_meta_type(&self) -> TokenStream { + quote! { light_account_pinocchio::solana_instruction::AccountMeta } + } + + fn is_pinocchio(&self) -> bool { + true + } + + fn sdk_error_type(&self) -> TokenStream { + quote! { light_account_pinocchio::LightSdkTypesError } + } + + fn program_error_type(&self) -> TokenStream { + quote! { pinocchio::program_error::ProgramError } + } + + fn borrow_error(&self) -> TokenStream { + quote! { .map_err(|_| light_account_pinocchio::LightSdkTypesError::Borsh)? } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_anchor_backend_types() { + let backend = AnchorBackend; + assert!(!backend.is_pinocchio()); + } + + #[test] + fn test_pinocchio_backend_types() { + let backend = PinocchioBackend; + assert!(backend.is_pinocchio()); + } +} diff --git a/sdk-libs/macros/src/light_pdas/mod.rs b/sdk-libs/macros/src/light_pdas/mod.rs index d707a9c73f..5dd6a3a2c1 100644 --- a/sdk-libs/macros/src/light_pdas/mod.rs +++ b/sdk-libs/macros/src/light_pdas/mod.rs @@ -11,6 +11,7 @@ pub mod account; pub mod accounts; +pub mod backend; pub mod light_account_keywords; pub mod parsing; pub mod program; diff --git a/sdk-libs/macros/src/light_pdas/program/compress.rs b/sdk-libs/macros/src/light_pdas/program/compress.rs index 305c47a8b3..e37c7b8d5f 100644 --- a/sdk-libs/macros/src/light_pdas/program/compress.rs +++ b/sdk-libs/macros/src/light_pdas/program/compress.rs @@ -8,7 +8,7 @@ use quote::quote; use syn::{Result, Type}; use super::parsing::InstructionVariant; -use crate::light_pdas::shared_utils::qualify_type_with_crate; +use crate::light_pdas::{backend::CodegenBackend, shared_utils::qualify_type_with_crate}; // ============================================================================= // COMPRESS BUILDER @@ -310,188 +310,157 @@ impl CompressBuilder { }) } - /// Generate compress dispatch as an associated function on the enum. - /// - /// When `#[derive(LightProgram)]` is used, the dispatch function is generated - /// as `impl EnumName { pub fn compress_dispatch(...) }` so it can be referenced - /// as `EnumName::compress_dispatch` and passed to SDK functions. - pub fn generate_enum_dispatch_method(&self, enum_name: &syn::Ident) -> Result { - let compress_arms: Vec<_> = self.accounts.iter().map(|info| { - let name = qualify_type_with_crate(&info.account_type); - - if info.is_zero_copy { - quote! { - d if d == #name::LIGHT_DISCRIMINATOR => { - let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; - let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); - drop(data); - light_account::prepare_account_for_compression( - account_info, &mut account_data, meta, index, ctx, - ) + /// Generate compress dispatch as an associated function on the enum using the specified backend. + pub fn generate_enum_dispatch_method_with_backend( + &self, + enum_name: &syn::Ident, + backend: &dyn CodegenBackend, + ) -> Result { + let account_crate = backend.account_crate(); + let account_info_type = backend.account_info_type(); + let sdk_error = backend.sdk_error_type(); + let borrow_error = backend.borrow_error(); + + let compress_arms: Vec<_> = self + .accounts + .iter() + .map(|info| { + let name = qualify_type_with_crate(&info.account_type); + + if info.is_zero_copy { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; + let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); + drop(data); + #account_crate::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } + } + } else { + quote! { + d if d == #name::LIGHT_DISCRIMINATOR => { + let mut reader = &data[8..]; + let mut account_data = #name::deserialize(&mut reader) + .map_err(|_| #sdk_error::InvalidInstructionData)?; + drop(data); + #account_crate::prepare_account_for_compression( + account_info, &mut account_data, meta, index, ctx, + ) + } } } - } else { - quote! { - d if d == #name::LIGHT_DISCRIMINATOR => { - let mut reader = &data[8..]; - let mut account_data = #name::deserialize(&mut reader) - .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; - drop(data); - light_account::prepare_account_for_compression( - account_info, &mut account_data, meta, index, ctx, - ) + }) + .collect(); + + if backend.is_pinocchio() { + Ok(quote! { + impl #enum_name { + pub fn compress_dispatch( + account_info: &#account_info_type, + meta: &#account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress, + index: usize, + ctx: &mut #account_crate::CompressCtx<'_>, + ) -> std::result::Result<(), #sdk_error> { + use #account_crate::LightDiscriminator; + use borsh::BorshDeserialize; + let data = account_info.try_borrow_data()#borrow_error; + let discriminator: [u8; 8] = data[..8] + .try_into() + .map_err(|_| #sdk_error::InvalidInstructionData)?; + match discriminator { + #(#compress_arms)* + _ => Ok(()), + } } } - } - }).collect(); - - Ok(quote! { - impl #enum_name { - pub fn compress_dispatch<'info>( - account_info: &anchor_lang::prelude::AccountInfo<'info>, - meta: &light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, - index: usize, - ctx: &mut light_account::CompressCtx<'_, 'info>, - ) -> std::result::Result<(), light_account::LightSdkTypesError> { - use light_account::LightDiscriminator; - use borsh::BorshDeserialize; - let data = account_info.try_borrow_data()?; - let discriminator: [u8; 8] = data[..8] - .try_into() - .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData)?; - match discriminator { - #(#compress_arms)* - _ => Ok(()), + }) + } else { + Ok(quote! { + impl #enum_name { + pub fn compress_dispatch<'info>( + account_info: &anchor_lang::prelude::AccountInfo<'info>, + meta: &#account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress, + index: usize, + ctx: &mut #account_crate::CompressCtx<'_, 'info>, + ) -> std::result::Result<(), #sdk_error> { + use #account_crate::LightDiscriminator; + use borsh::BorshDeserialize; + let data = account_info.try_borrow_data()#borrow_error; + let discriminator: [u8; 8] = data[..8] + .try_into() + .map_err(|_| #sdk_error::InvalidInstructionData)?; + match discriminator { + #(#compress_arms)* + _ => Ok(()), + } } } - } - }) + }) + } } - // ------------------------------------------------------------------------- - // Pinocchio Code Generation Methods - // ------------------------------------------------------------------------- - - /// Generate compress dispatch as an associated function on the enum (pinocchio version). - /// - /// Same logic as `generate_enum_dispatch_method()` but with pinocchio types: - /// - `pinocchio::account_info::AccountInfo` instead of `anchor_lang::prelude::AccountInfo` - /// - `light_account_pinocchio::` instead of `light_account::` - pub fn generate_enum_dispatch_method_pinocchio( + /// Generate `process_compress` as an enum associated function using the specified backend. + pub fn generate_enum_process_compress_with_backend( &self, enum_name: &syn::Ident, + backend: &dyn CodegenBackend, ) -> Result { - let compress_arms: Vec<_> = self.accounts.iter().map(|info| { - let name = qualify_type_with_crate(&info.account_type); - - if info.is_zero_copy { - quote! { - d if d == #name::LIGHT_DISCRIMINATOR => { - let pod_bytes = &data[8..8 + core::mem::size_of::<#name>()]; - let mut account_data: #name = *bytemuck::from_bytes(pod_bytes); - drop(data); - light_account_pinocchio::prepare_account_for_compression( - account_info, &mut account_data, meta, index, ctx, + let account_crate = backend.account_crate(); + let program_error = backend.program_error_type(); + + if backend.is_pinocchio() { + Ok(quote! { + impl #enum_name { + pub fn process_compress( + accounts: &[pinocchio::account_info::AccountInfo], + instruction_data: &[u8], + ) -> std::result::Result<(), #program_error> { + use borsh::BorshDeserialize; + let params = #account_crate::CompressAndCloseParams::try_from_slice(instruction_data) + .map_err(|_| #program_error::InvalidInstructionData)?; + #account_crate::process_compress_pda_accounts_idempotent( + accounts, + ¶ms, + Self::compress_dispatch, + crate::LIGHT_CPI_SIGNER, + &crate::LIGHT_CPI_SIGNER.program_id, ) + .map_err(|e| #program_error::Custom(u32::from(e))) } } - } else { - quote! { - d if d == #name::LIGHT_DISCRIMINATOR => { - let mut reader = &data[8..]; - let mut account_data = #name::deserialize(&mut reader) - .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)?; - drop(data); - light_account_pinocchio::prepare_account_for_compression( - account_info, &mut account_data, meta, index, ctx, - ) - } - } - } - }).collect(); - - Ok(quote! { - impl #enum_name { - pub fn compress_dispatch( - account_info: &pinocchio::account_info::AccountInfo, - meta: &light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, - index: usize, - ctx: &mut light_account_pinocchio::CompressCtx<'_>, - ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { - use light_account_pinocchio::LightDiscriminator; - use borsh::BorshDeserialize; - let data = account_info.try_borrow_data() - .map_err(|_| light_account_pinocchio::LightSdkTypesError::Borsh)?; - let discriminator: [u8; 8] = data[..8] - .try_into() - .map_err(|_| light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)?; - match discriminator { - #(#compress_arms)* - _ => Ok(()), - } - } - } - }) + }) + } else { + // Anchor version doesn't have this method on enum - it uses the separate processor + Ok(quote! {}) + } } - /// Generate `process_compress` as an enum associated function (pinocchio version). - /// - /// The function deserializes params from instruction_data before calling the processor. - pub fn generate_enum_process_compress_pinocchio( + /// Generate compile-time size validation for compressed accounts using the specified backend. + pub fn generate_size_validation_with_backend( &self, - enum_name: &syn::Ident, + backend: &dyn CodegenBackend, ) -> Result { - Ok(quote! { - impl #enum_name { - pub fn process_compress( - accounts: &[pinocchio::account_info::AccountInfo], - instruction_data: &[u8], - ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { - use borsh::BorshDeserialize; - let params = light_account_pinocchio::CompressAndCloseParams::try_from_slice(instruction_data) - .map_err(|_| pinocchio::program_error::ProgramError::InvalidInstructionData)?; - light_account_pinocchio::process_compress_pda_accounts_idempotent( - accounts, - ¶ms, - Self::compress_dispatch, - crate::LIGHT_CPI_SIGNER, - &crate::LIGHT_CPI_SIGNER.program_id, - ) - .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) - } - } - }) - } + let account_crate = backend.account_crate(); - /// Generate compile-time size validation for compressed accounts (pinocchio version). - /// Uses INIT_SPACE directly instead of CompressedInitSpace trait. - pub fn generate_size_validation_pinocchio(&self) -> Result { let size_checks: Vec<_> = self.accounts.iter().map(|info| { let qualified_type = qualify_type_with_crate(&info.account_type); - // For pinocchio, all types use INIT_SPACE constant (no CompressedInitSpace trait) - quote! { - const _: () = { - const COMPRESSED_SIZE: usize = 8 + #qualified_type::INIT_SPACE; - assert!( - COMPRESSED_SIZE <= 800, - concat!( - "Compressed account '", stringify!(#qualified_type), "' exceeds 800-byte compressible account size limit" - ) - ); - }; - } - }).collect(); - - Ok(quote! { #(#size_checks)* }) - } - - /// Generate compile-time size validation for compressed accounts. - pub fn generate_size_validation(&self) -> Result { - let size_checks: Vec<_> = self.accounts.iter().map(|info| { - let qualified_type = qualify_type_with_crate(&info.account_type); - - if info.is_zero_copy { + if backend.is_pinocchio() { + // For pinocchio, all types use INIT_SPACE constant (no CompressedInitSpace trait) + quote! { + const _: () = { + const COMPRESSED_SIZE: usize = 8 + #qualified_type::INIT_SPACE; + assert!( + COMPRESSED_SIZE <= 800, + concat!( + "Compressed account '", stringify!(#qualified_type), "' exceeds 800-byte compressible account size limit" + ) + ); + }; + } + } else if info.is_zero_copy { // For Pod types, use core::mem::size_of for size calculation quote! { const _: () = { @@ -507,7 +476,7 @@ impl CompressBuilder { // For Borsh types, use CompressedInitSpace trait quote! { const _: () = { - const COMPRESSED_SIZE: usize = 8 + <#qualified_type as light_account::compression_info::CompressedInitSpace>::COMPRESSED_INIT_SPACE; + const COMPRESSED_SIZE: usize = 8 + <#qualified_type as #account_crate::compression_info::CompressedInitSpace>::COMPRESSED_INIT_SPACE; if COMPRESSED_SIZE > 800 { panic!(concat!( "Compressed account '", stringify!(#qualified_type), "' exceeds 800-byte compressible account size limit. If you need support for larger accounts, send a message to team@lightprotocol.com" diff --git a/sdk-libs/macros/src/light_pdas/program/decompress.rs b/sdk-libs/macros/src/light_pdas/program/decompress.rs index c3668631f4..dae10bb627 100644 --- a/sdk-libs/macros/src/light_pdas/program/decompress.rs +++ b/sdk-libs/macros/src/light_pdas/program/decompress.rs @@ -14,7 +14,10 @@ use super::{ seed_utils::ctx_fields_to_set, variant_enum::PdaCtxSeedInfo, }; -use crate::light_pdas::shared_utils::{is_constant_identifier, qualify_type_with_crate}; +use crate::light_pdas::{ + backend::CodegenBackend, + shared_utils::{is_constant_identifier, qualify_type_with_crate}, +}; // ============================================================================= // DECOMPRESS BUILDER @@ -251,9 +254,11 @@ impl DecompressBuilder { }) } - /// Generate PDA seed provider implementations. - /// Returns empty Vec for mint-only or token-only programs that have no PDA seeds. - pub fn generate_seed_provider_impls(&self, is_pinocchio: bool) -> Result> { + /// Generate PDA seed provider implementations using the specified backend. + pub fn generate_seed_provider_impls_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> Result> { // For mint-only or token-only programs, there are no PDA seeds - return empty Vec let pda_seed_specs = match self.pda_seeds.as_ref() { Some(specs) if !specs.is_empty() => specs, @@ -282,6 +287,7 @@ impl DecompressBuilder { }; let mut results = Vec::with_capacity(self.pda_ctx_seeds.len()); + let account_crate = backend.account_crate(); for ctx_info in self.pda_ctx_seeds.iter() { let variant_str = ctx_info.variant_name.to_string(); @@ -302,7 +308,7 @@ impl DecompressBuilder { let ctx_fields_decl: Vec<_> = ctx_fields .iter() .map(|field| { - if is_pinocchio { + if backend.is_pinocchio() { quote! { pub #field: [u8; 32] } } else { quote! { pub #field: solana_pubkey::Pubkey } @@ -330,15 +336,10 @@ impl DecompressBuilder { ctx_fields, &ctx_info.state_field_names, params_only_fields, - is_pinocchio, + backend.is_pinocchio(), )?; let has_params_only = !params_only_fields.is_empty(); - let account_crate = if is_pinocchio { - quote! { light_account_pinocchio } - } else { - quote! { light_account } - }; let seed_params_impl = if has_params_only { quote! { #ctx_seeds_struct @@ -377,102 +378,94 @@ impl DecompressBuilder { } // ------------------------------------------------------------------------- - // Pinocchio Code Generation Methods + // Backend-Aware Code Generation Methods // ------------------------------------------------------------------------- - /// Generate `process_decompress` as an enum associated function (pinocchio version). - /// - /// The function deserializes params from instruction_data before calling the processor. - pub fn generate_enum_process_decompress_pinocchio( + /// Generate `process_decompress` as an enum associated function using the specified backend. + pub fn generate_enum_process_decompress_with_backend( &self, enum_name: &syn::Ident, + backend: &dyn CodegenBackend, ) -> Result { - let processor_call = if self.has_tokens { - quote! { - light_account_pinocchio::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( - accounts, - ¶ms, - crate::LIGHT_CPI_SIGNER, - &crate::LIGHT_CPI_SIGNER.program_id, - current_slot, - ) - } + let account_crate = backend.account_crate(); + let program_error = backend.program_error_type(); + + let processor_fn = if self.has_tokens { + quote! { process_decompress_accounts_idempotent } } else { - quote! { - light_account_pinocchio::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( - accounts, - ¶ms, - crate::LIGHT_CPI_SIGNER, - &crate::LIGHT_CPI_SIGNER.program_id, - current_slot, - ) - } + quote! { process_decompress_pda_accounts_idempotent } }; - Ok(quote! { - impl #enum_name { - pub fn process_decompress( - accounts: &[pinocchio::account_info::AccountInfo], - instruction_data: &[u8], - ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { - use borsh::BorshDeserialize; - use pinocchio::sysvars::Sysvar; - let params = light_account_pinocchio::DecompressIdempotentParams::::try_from_slice(instruction_data) - .map_err(|_| pinocchio::program_error::ProgramError::InvalidInstructionData)?; - let current_slot = pinocchio::sysvars::clock::Clock::get() - .map_err(|_| pinocchio::program_error::ProgramError::UnsupportedSysvar)? - .slot; - #processor_call - .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + if backend.is_pinocchio() { + Ok(quote! { + impl #enum_name { + pub fn process_decompress( + accounts: &[pinocchio::account_info::AccountInfo], + instruction_data: &[u8], + ) -> std::result::Result<(), #program_error> { + use borsh::BorshDeserialize; + use pinocchio::sysvars::Sysvar; + let params = #account_crate::DecompressIdempotentParams::::try_from_slice(instruction_data) + .map_err(|_| #program_error::InvalidInstructionData)?; + let current_slot = pinocchio::sysvars::clock::Clock::get() + .map_err(|_| #program_error::UnsupportedSysvar)? + .slot; + #account_crate::#processor_fn::<_, PackedLightAccountVariant>( + accounts, + ¶ms, + crate::LIGHT_CPI_SIGNER, + &crate::LIGHT_CPI_SIGNER.program_id, + current_slot, + ) + .map_err(|e| #program_error::Custom(u32::from(e))) + } } - } - }) + }) + } else { + // Anchor version doesn't generate process_decompress on enum - uses separate processor + Ok(quote! {}) + } } - /// Generate decompress dispatch as an associated function on the enum. - /// - /// When `#[derive(LightProgram)]` is used, the dispatch function is generated - /// as `impl EnumName { pub fn decompress_dispatch(...) }` so it can be referenced - /// as `EnumName::decompress_dispatch`. - /// - /// This wraps the type-parameter-based SDK call, binding `PackedLightAccountVariant` - /// as the concrete type. - pub fn generate_enum_decompress_dispatch(&self, enum_name: &syn::Ident) -> Result { - let processor_call = if self.has_tokens { - quote! { - light_account::process_decompress_accounts_idempotent::<_, PackedLightAccountVariant>( - remaining_accounts, - params, - cpi_signer, - program_id, - current_slot, - ) - } + /// Generate decompress dispatch as an associated function on the enum using the specified backend. + pub fn generate_enum_decompress_dispatch_with_backend( + &self, + enum_name: &syn::Ident, + backend: &dyn CodegenBackend, + ) -> Result { + let account_crate = backend.account_crate(); + let sdk_error = backend.sdk_error_type(); + + let processor_fn = if self.has_tokens { + quote! { process_decompress_accounts_idempotent } } else { - quote! { - light_account::process_decompress_pda_accounts_idempotent::<_, PackedLightAccountVariant>( - remaining_accounts, - params, - cpi_signer, - program_id, - current_slot, - ) - } + quote! { process_decompress_pda_accounts_idempotent } }; - Ok(quote! { - impl #enum_name { - pub fn decompress_dispatch<'info>( - remaining_accounts: &[solana_account_info::AccountInfo<'info>], - params: &light_account::DecompressIdempotentParams, - cpi_signer: light_account::CpiSigner, - program_id: &[u8; 32], - current_slot: u64, - ) -> std::result::Result<(), light_account::LightSdkTypesError> { - #processor_call + if backend.is_pinocchio() { + // Pinocchio uses generate_enum_process_decompress instead + Ok(quote! {}) + } else { + Ok(quote! { + impl #enum_name { + pub fn decompress_dispatch<'info>( + remaining_accounts: &[solana_account_info::AccountInfo<'info>], + params: &#account_crate::DecompressIdempotentParams, + cpi_signer: #account_crate::CpiSigner, + program_id: &[u8; 32], + current_slot: u64, + ) -> std::result::Result<(), #sdk_error> { + #account_crate::#processor_fn::<_, PackedLightAccountVariant>( + remaining_accounts, + params, + cpi_signer, + program_id, + current_slot, + ) + } } - } - }) + }) + } } } diff --git a/sdk-libs/macros/src/light_pdas/program/instructions.rs b/sdk-libs/macros/src/light_pdas/program/instructions.rs index 20691f874a..d9d64edb83 100644 --- a/sdk-libs/macros/src/light_pdas/program/instructions.rs +++ b/sdk-libs/macros/src/light_pdas/program/instructions.rs @@ -23,7 +23,10 @@ pub use super::{ variant_enum::PdaCtxSeedInfo, }; use crate::{ - light_pdas::shared_utils::{ident_to_type, qualify_type_with_crate}, + light_pdas::{ + backend::{AnchorBackend, CodegenBackend, PinocchioBackend}, + shared_utils::{ident_to_type, qualify_type_with_crate}, + }, utils::to_snake_case, }; @@ -47,6 +50,38 @@ pub(crate) fn generate_light_program_items( has_ata_fields: bool, pda_variant_code: TokenStream, enum_name: Option<&syn::Ident>, +) -> Result> { + generate_light_program_items_with_backend( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + crate_ctx, + has_mint_fields, + has_ata_fields, + pda_variant_code, + enum_name, + &AnchorBackend, + ) +} + +/// Unified code generation with backend abstraction. +/// +/// This function contains all the shared logic between Anchor and Pinocchio code generation, +/// using the `CodegenBackend` trait to handle framework-specific differences. +#[inline(never)] +#[allow(clippy::too_many_arguments)] +pub(crate) fn generate_light_program_items_with_backend( + compressible_accounts: Vec, + pda_seeds: Option>, + token_seeds: Option>, + instruction_data: Vec, + crate_ctx: &crate::light_pdas::parsing::CrateContext, + has_mint_fields: bool, + has_ata_fields: bool, + pda_variant_code: TokenStream, + enum_name: Option<&syn::Ident>, + backend: &dyn CodegenBackend, ) -> Result> { // TODO: Unify seed extraction - currently #[light_program] extracts seeds from Anchor's // #[account(seeds = [...])] automatically, while #[derive(LightAccounts)] requires @@ -111,11 +146,15 @@ pub(crate) fn generate_light_program_items( // Generate variant enum and traits only if there are PDA seeds // For mint-only programs (no PDA state accounts), generate minimal placeholder code + let account_crate = backend.account_crate(); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let enum_and_traits = if pda_ctx_seeds.is_empty() { // Generate minimal code for mint-only programs that matches trait signatures quote! { /// Placeholder enum for programs that only use Light mints without state accounts. - #[derive(Clone, Debug, anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] + #[derive(Clone, Debug, #serialize_derive, #deserialize_derive)] pub enum LightAccountVariant { /// Placeholder variant for mint-only programs Empty, @@ -127,70 +166,70 @@ pub(crate) fn generate_light_program_items( } } - impl light_account::hasher::DataHasher for LightAccountVariant { - fn hash(&self) -> std::result::Result<[u8; 32], light_account::hasher::HasherError> { + impl #account_crate::hasher::DataHasher for LightAccountVariant { + fn hash(&self) -> std::result::Result<[u8; 32], #account_crate::hasher::HasherError> { match self { - Self::Empty => Err(light_account::hasher::HasherError::EmptyInput), + Self::Empty => Err(#account_crate::hasher::HasherError::EmptyInput), } } } - impl light_account::LightDiscriminator for LightAccountVariant { + impl #account_crate::LightDiscriminator for LightAccountVariant { const LIGHT_DISCRIMINATOR: [u8; 8] = [0; 8]; const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; } - impl light_account::HasCompressionInfo for LightAccountVariant { - fn compression_info(&self) -> std::result::Result<&light_account::CompressionInfo, light_account::LightSdkTypesError> { - Err(light_account::LightSdkTypesError::InvalidInstructionData) + impl #account_crate::HasCompressionInfo for LightAccountVariant { + fn compression_info(&self) -> std::result::Result<&#account_crate::CompressionInfo, #account_crate::LightSdkTypesError> { + Err(#account_crate::LightSdkTypesError::InvalidInstructionData) } - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account::CompressionInfo, light_account::LightSdkTypesError> { - Err(light_account::LightSdkTypesError::InvalidInstructionData) + fn compression_info_mut(&mut self) -> std::result::Result<&mut #account_crate::CompressionInfo, #account_crate::LightSdkTypesError> { + Err(#account_crate::LightSdkTypesError::InvalidInstructionData) } - fn compression_info_mut_opt(&mut self) -> &mut Option { + fn compression_info_mut_opt(&mut self) -> &mut Option<#account_crate::CompressionInfo> { panic!("compression_info_mut_opt not supported for mint-only programs") } - fn set_compression_info_none(&mut self) -> std::result::Result<(), light_account::LightSdkTypesError> { - Err(light_account::LightSdkTypesError::InvalidInstructionData) + fn set_compression_info_none(&mut self) -> std::result::Result<(), #account_crate::LightSdkTypesError> { + Err(#account_crate::LightSdkTypesError::InvalidInstructionData) } } - impl light_account::Size for LightAccountVariant { - fn size(&self) -> std::result::Result { - Err(light_account::LightSdkTypesError::InvalidInstructionData) + impl #account_crate::Size for LightAccountVariant { + fn size(&self) -> std::result::Result { + Err(#account_crate::LightSdkTypesError::InvalidInstructionData) } } // Pack trait is only available off-chain (client-side) #[cfg(not(target_os = "solana"))] - impl light_account::Pack for LightAccountVariant { + impl #account_crate::Pack for LightAccountVariant { type Packed = Self; - fn pack(&self, _remaining_accounts: &mut light_account::interface::instruction::PackedAccounts) -> std::result::Result { + fn pack(&self, _remaining_accounts: &mut #account_crate::interface::instruction::PackedAccounts) -> std::result::Result { Ok(Self::Empty) } } - impl light_account::Unpack for LightAccountVariant { + impl #account_crate::Unpack for LightAccountVariant { type Unpacked = Self; - fn unpack(&self, _remaining_accounts: &[AI]) -> std::result::Result { + fn unpack(&self, _remaining_accounts: &[AI]) -> std::result::Result { Ok(Self::Empty) } } /// Wrapper for compressed account data (mint-only placeholder). - #[derive(Clone, Debug, anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] + #[derive(Clone, Debug, #serialize_derive, #deserialize_derive)] pub struct LightAccountData { - pub meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub meta: #account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress, pub data: LightAccountVariant, } impl Default for LightAccountData { fn default() -> Self { Self { - meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), + meta: #account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), data: LightAccountVariant::default(), } } @@ -211,7 +250,7 @@ pub(crate) fn generate_light_program_items( } else { builder }; - builder.build()? + builder.build_with_backend(backend)? }; // Collect all unique params-only seed fields across all variants for SeedParams struct @@ -229,7 +268,7 @@ pub(crate) fn generate_light_program_items( let seed_params_struct = if all_params_only_fields.is_empty() { quote! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug, Default)] + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug, Default)] pub struct SeedParams; } } else { @@ -250,7 +289,7 @@ pub(crate) fn generate_light_program_items( }) .collect(); quote! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct SeedParams { #(#seed_param_fields,)* } @@ -264,10 +303,8 @@ pub(crate) fn generate_light_program_items( } }; - let _instruction_data_types: std::collections::HashMap = instruction_data - .iter() - .map(|spec| (spec.field_name.to_string(), &spec.field_type)) - .collect(); + let sdk_error_type = backend.sdk_error_type(); + let program_error_type = backend.program_error_type(); let seeds_structs_and_constructors: Vec = if let Some(ref pda_seed_specs) = pda_seeds @@ -286,30 +323,69 @@ pub(crate) fn generate_light_program_items( let _ctx_fields = &ctx_info.ctx_seed_fields; let _params_only_fields = &ctx_info.params_only_seed_fields; let data_fields = extract_data_seed_fields(&spec.seeds); - // Only generate verifications for data fields that exist on the state struct - let data_verifications: Vec<_> = data_fields.iter().filter_map(|field| { - let field_str = field.to_string(); - // Skip fields that don't exist on the state struct (e.g., params-only seeds) - if !ctx_info.state_field_names.contains(&field_str) { - return None; - } - Some(quote! { - if data.#field != seeds.#field { - return std::result::Result::Err(LightInstructionError::SeedMismatch.into()); - } - }) - }).collect(); - // Both zero_copy and Borsh accounts use AnchorDeserialize on the full - // compressed data (which includes CompressionInfo::compressed()). - let (deserialize_code, variant_data) = ( + // Data verifications and deserialization differ by backend + let (data_verifications, deserialize_code, variant_data, return_type) = + if backend.is_pinocchio() { + // Pinocchio: use BorshDeserialize with light_account_pinocchio errors + let verifications: Vec<_> = data_fields.iter().filter_map(|field| { + let field_str = field.to_string(); + if !ctx_info.state_field_names.contains(&field_str) { + return None; + } + Some(quote! { + if data.#field != seeds.#field { + return std::result::Result::Err( + #sdk_error_type::InvalidInstructionData + ); + } + }) + }).collect(); + + let deser = quote! { + use borsh::BorshDeserialize; + let data: #inner_type = BorshDeserialize::deserialize(&mut &account_data[..]) + .map_err(|_| #sdk_error_type::Borsh)?; + }; + + (verifications, deser, quote! { data }, quote! { #sdk_error_type }) + } else { + // Anchor: use AnchorDeserialize with anchor errors + let verifications: Vec<_> = data_fields.iter().filter_map(|field| { + let field_str = field.to_string(); + if !ctx_info.state_field_names.contains(&field_str) { + return None; + } + Some(quote! { + if data.#field != seeds.#field { + return std::result::Result::Err(LightInstructionError::SeedMismatch.into()); + } + }) + }).collect(); + + let deser = quote! { + use anchor_lang::AnchorDeserialize; + let data: #inner_type = AnchorDeserialize::deserialize(&mut &account_data[..]) + .map_err(|_| anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountDidNotDeserialize))?; + }; + + (verifications, deser, quote! { data }, quote! { #program_error_type }) + }; + + // For Pinocchio, the constructor already returns LightSdkTypesError (same + // as IntoVariant's error type), so pass through directly to preserve + // specific error variants (e.g. Borsh). For Anchor, the constructor + // returns anchor_lang::error::Error which needs type conversion. + let into_variant_body = if backend.is_pinocchio() { quote! { - use anchor_lang::AnchorDeserialize; - let data: #inner_type = AnchorDeserialize::deserialize(&mut &account_data[..]) - .map_err(|_| anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountDidNotDeserialize))?; - }, - quote! { data }, - ); + LightAccountVariant::#constructor_name(data, self) + } + } else { + quote! { + LightAccountVariant::#constructor_name(data, self) + .map_err(|_| #sdk_error_type::InvalidInstructionData) + } + }; let generated = quote! { impl LightAccountVariant { @@ -317,7 +393,7 @@ pub(crate) fn generate_light_program_items( pub fn #constructor_name( account_data: &[u8], seeds: #seeds_struct_name, - ) -> std::result::Result { + ) -> std::result::Result { #deserialize_code #(#data_verifications)* @@ -329,10 +405,9 @@ pub(crate) fn generate_light_program_items( }) } } - impl light_account::IntoVariant for #seeds_struct_name { - fn into_variant(self, data: &[u8]) -> std::result::Result { - LightAccountVariant::#constructor_name(data, self) - .map_err(|_| light_account::LightSdkTypesError::InvalidInstructionData) + impl #account_crate::IntoVariant for #seeds_struct_name { + fn into_variant(self, data: &[u8]) -> std::result::Result { + #into_variant_body } } }; @@ -365,8 +440,13 @@ pub(crate) fn generate_light_program_items( let compress_builder = CompressBuilder::new(compressible_accounts.clone(), instruction_variant); compress_builder.validate()?; - let size_validation_checks = compress_builder.generate_size_validation()?; - let error_codes = compress_builder.generate_error_codes()?; + let size_validation_checks = compress_builder.generate_size_validation_with_backend(backend)?; + // Error codes are only generated for Anchor + let error_codes = if !backend.is_pinocchio() { + Some(compress_builder.generate_error_codes()?) + } else { + None + }; // Create DecompressBuilder to generate all decompress-related code let decompress_builder = DecompressBuilder::new( @@ -376,192 +456,246 @@ pub(crate) fn generate_light_program_items( ); // Note: DecompressBuilder validation is optional for now since pda_seeds may be empty for TokenOnly - let decompress_accounts = decompress_builder.generate_accounts_struct()?; - let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls(false)?; + // Accounts structs and seed provider impls differ by backend + let decompress_accounts = if !backend.is_pinocchio() { + Some(decompress_builder.generate_accounts_struct()?) + } else { + None + }; + let pda_seed_provider_impls = + decompress_builder.generate_seed_provider_impls_with_backend(backend)?; // Generate trait impls and decompress processor/instruction based on program type. + // These are only generated for Anchor - Pinocchio programs use enum associated functions instead. // v2 interface: no DecompressContext trait needed - uses DecompressVariant on PackedLightAccountVariant. - let (trait_impls, decompress_processor_fn, decompress_instruction) = - if !pda_ctx_seeds.is_empty() && has_token_seeds_early { - // Mixed program: PDAs + Tokens - generate full impl with token checking. - // Token variants are now first-class members of PackedLightAccountVariant, - // so we match against the individual token variant names. - let token_variant_names: Vec<_> = token_seeds - .as_ref() - .map(|specs| specs.iter().map(|s| &s.variant).collect()) - .unwrap_or_default(); - - let token_match_arms: Vec<_> = token_variant_names - .iter() - .map(|name| quote! { PackedLightAccountVariant::#name(_) => true, }) - .collect(); - - let trait_impls: syn::ItemMod = syn::parse_quote! { - mod __trait_impls { - use super::*; - - impl light_account::HasTokenVariant for LightAccountData { - fn is_packed_token(&self) -> bool { - match &self.data { - #(#token_match_arms)* - _ => false, - } + let (trait_impls, decompress_processor_fn, decompress_instruction) = if backend.is_pinocchio() { + // Pinocchio: no trait impls, processor module, or instruction handlers + (None, None, None) + } else if !pda_ctx_seeds.is_empty() && has_token_seeds_early { + // Anchor Mixed program: PDAs + Tokens - generate full impl with token checking. + // Token variants are now first-class members of PackedLightAccountVariant, + // so we match against the individual token variant names. + let token_variant_names: Vec<_> = token_seeds + .as_ref() + .map(|specs| specs.iter().map(|s| &s.variant).collect()) + .unwrap_or_default(); + + let token_match_arms: Vec<_> = token_variant_names + .iter() + .map(|name| quote! { PackedLightAccountVariant::#name(_) => true, }) + .collect(); + + let trait_impls: syn::ItemMod = syn::parse_quote! { + mod __trait_impls { + use super::*; + + impl #account_crate::HasTokenVariant for LightAccountData { + fn is_packed_token(&self) -> bool { + match &self.data { + #(#token_match_arms)* + _ => false, } } } - }; - let decompress_processor_fn = decompress_builder.generate_processor()?; - let decompress_instruction = decompress_builder.generate_entrypoint()?; - ( - Some(trait_impls), - Some(decompress_processor_fn), - Some(decompress_instruction), - ) - } else if !pda_ctx_seeds.is_empty() { - // PDA-only program: simplified impl without token checking - let trait_impls: syn::ItemMod = syn::parse_quote! { - mod __trait_impls { - use super::*; - - impl light_account::HasTokenVariant for LightAccountData { - fn is_packed_token(&self) -> bool { - // PDA-only programs have no token variants - false - } + } + }; + let decompress_processor_fn = decompress_builder.generate_processor()?; + let decompress_instruction = decompress_builder.generate_entrypoint()?; + ( + Some(trait_impls), + Some(decompress_processor_fn), + Some(decompress_instruction), + ) + } else if !pda_ctx_seeds.is_empty() { + // Anchor PDA-only program: simplified impl without token checking + let trait_impls: syn::ItemMod = syn::parse_quote! { + mod __trait_impls { + use super::*; + + impl #account_crate::HasTokenVariant for LightAccountData { + fn is_packed_token(&self) -> bool { + // PDA-only programs have no token variants + false } } - }; - let decompress_processor_fn = decompress_builder.generate_processor()?; - let decompress_instruction = decompress_builder.generate_entrypoint()?; - ( - Some(trait_impls), - Some(decompress_processor_fn), - Some(decompress_instruction), - ) - } else { - // Mint-only programs: placeholder impl - let trait_impls: syn::ItemMod = syn::parse_quote! { - mod __trait_impls { - use super::*; - - impl light_account::HasTokenVariant for LightAccountData { - fn is_packed_token(&self) -> bool { - match &self.data { - LightAccountVariant::Empty => false, - _ => true, - } + } + }; + let decompress_processor_fn = decompress_builder.generate_processor()?; + let decompress_instruction = decompress_builder.generate_entrypoint()?; + ( + Some(trait_impls), + Some(decompress_processor_fn), + Some(decompress_instruction), + ) + } else { + // Anchor Mint-only programs: placeholder impl + let trait_impls: syn::ItemMod = syn::parse_quote! { + mod __trait_impls { + use super::*; + + impl #account_crate::HasTokenVariant for LightAccountData { + fn is_packed_token(&self) -> bool { + match &self.data { + LightAccountVariant::Empty => false, + _ => true, } } } - }; - (Some(trait_impls), None, None) + } }; + (Some(trait_impls), None, None) + }; - let compress_accounts = compress_builder.generate_accounts_struct()?; - let compress_dispatch_fn = compress_builder.generate_dispatch_fn()?; - let compress_processor_fn = compress_builder.generate_processor()?; - let compress_instruction = compress_builder.generate_entrypoint()?; - - // Generate processor module - includes dispatch fn + processor fns. - // The compress dispatch function must be inside the module so it can - // access `use super::*` imports. - let processor_module: syn::ItemMod = - if let Some(decompress_processor_fn) = decompress_processor_fn { - syn::parse_quote! { - mod __processor_functions { - use super::*; - #compress_dispatch_fn - #decompress_processor_fn - #compress_processor_fn + // Anchor-only: accounts structs, processor module, and config instructions + #[allow(unused_variables)] + let ( + compress_accounts, + compress_dispatch_fn, + compress_processor_fn, + compress_instruction, + processor_module, + init_config_accounts, + update_config_accounts, + init_config_instruction, + update_config_instruction, + ) = if !backend.is_pinocchio() { + let compress_accounts = compress_builder.generate_accounts_struct()?; + let compress_dispatch_fn = compress_builder.generate_dispatch_fn()?; + let compress_processor_fn = compress_builder.generate_processor()?; + let compress_instruction = compress_builder.generate_entrypoint()?; + + // Generate processor module - includes dispatch fn + processor fns. + // The compress dispatch function must be inside the module so it can + // access `use super::*` imports. + let processor_module: syn::ItemMod = + if let Some(ref decompress_processor_fn) = decompress_processor_fn { + syn::parse_quote! { + mod __processor_functions { + use super::*; + #compress_dispatch_fn + #decompress_processor_fn + #compress_processor_fn + } } - } - } else { - syn::parse_quote! { - mod __processor_functions { - use super::*; - #compress_dispatch_fn - #compress_processor_fn + } else { + syn::parse_quote! { + mod __processor_functions { + use super::*; + #compress_dispatch_fn + #compress_processor_fn + } } + }; + + let init_config_accounts: syn::ItemStruct = syn::parse_quote! { + #[derive(Accounts)] + pub struct InitializeCompressionConfig<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: Checked by SDK + #[account(mut)] + pub config: AccountInfo<'info>, + /// CHECK: Checked by SDK + pub program_data: AccountInfo<'info>, + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, } }; - let init_config_accounts: syn::ItemStruct = syn::parse_quote! { - #[derive(Accounts)] - pub struct InitializeCompressionConfig<'info> { - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: Checked by SDK - #[account(mut)] - pub config: AccountInfo<'info>, - /// CHECK: Checked by SDK - pub program_data: AccountInfo<'info>, - pub authority: Signer<'info>, - pub system_program: Program<'info, System>, - } - }; + let update_config_accounts: syn::ItemStruct = syn::parse_quote! { + #[derive(Accounts)] + pub struct UpdateCompressionConfig<'info> { + /// CHECK: Checked by SDK + #[account(mut)] + pub config: AccountInfo<'info>, + pub update_authority: Signer<'info>, + } + }; - let update_config_accounts: syn::ItemStruct = syn::parse_quote! { - #[derive(Accounts)] - pub struct UpdateCompressionConfig<'info> { - /// CHECK: Checked by SDK - #[account(mut)] - pub config: AccountInfo<'info>, - pub update_authority: Signer<'info>, - } - }; + let init_config_instruction: syn::ItemFn = syn::parse_quote! { + #[inline(never)] + pub fn initialize_compression_config<'info>( + ctx: Context<'_, '_, '_, 'info, InitializeCompressionConfig<'info>>, + params: InitConfigParams, + ) -> Result<()> { + #account_crate::process_initialize_light_config( + &ctx.accounts.config, + &ctx.accounts.authority, + ¶ms.rent_sponsor.to_bytes(), + ¶ms.compression_authority.to_bytes(), + params.rent_config, + params.write_top_up, + params.address_space.iter().map(|p| p.to_bytes()).collect(), + 0, // config_bump + &ctx.accounts.payer, + &ctx.accounts.system_program, + &crate::LIGHT_CPI_SIGNER.program_id, + ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; + Ok(()) + } + }; - let init_config_params_struct: syn::ItemStruct = syn::parse_quote! { - /// Configuration parameters for initializing compression config. - /// Field order matches SDK client's `InitializeCompressionConfigAnchorData`. - #[derive(AnchorSerialize, AnchorDeserialize, Clone)] - pub struct InitConfigParams { - pub write_top_up: u32, - pub rent_sponsor: Pubkey, - pub compression_authority: Pubkey, - pub rent_config: light_account::RentConfig, - pub address_space: Vec, - } - }; + let update_config_instruction: syn::ItemFn = syn::parse_quote! { + #[inline(never)] + pub fn update_compression_config<'info>( + ctx: Context<'_, '_, '_, 'info, UpdateCompressionConfig<'info>>, + instruction_data: Vec, + ) -> Result<()> { + let remaining = [ + ctx.accounts.config.to_account_info(), + ctx.accounts.update_authority.to_account_info(), + ]; + #account_crate::process_update_light_config( + &remaining, + &instruction_data, + &crate::LIGHT_CPI_SIGNER.program_id, + ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; + Ok(()) + } + }; - let init_config_instruction: syn::ItemFn = syn::parse_quote! { - #[inline(never)] - pub fn initialize_compression_config<'info>( - ctx: Context<'_, '_, '_, 'info, InitializeCompressionConfig<'info>>, - params: InitConfigParams, - ) -> Result<()> { - light_account::process_initialize_light_config( - &ctx.accounts.config, - &ctx.accounts.authority, - ¶ms.rent_sponsor.to_bytes(), - ¶ms.compression_authority.to_bytes(), - params.rent_config, - params.write_top_up, - params.address_space.iter().map(|p| p.to_bytes()).collect(), - 0, // config_bump - &ctx.accounts.payer, - &ctx.accounts.system_program, - &crate::LIGHT_CPI_SIGNER.program_id, - ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; - Ok(()) - } + ( + Some(compress_accounts), + Some(compress_dispatch_fn), + Some(compress_processor_fn), + Some(compress_instruction), + Some(processor_module), + Some(init_config_accounts), + Some(update_config_accounts), + Some(init_config_instruction), + Some(update_config_instruction), + ) + } else { + // Pinocchio: no accounts structs, processor module, or config instructions + (None, None, None, None, None, None, None, None, None) }; - let update_config_instruction: syn::ItemFn = syn::parse_quote! { - #[inline(never)] - pub fn update_compression_config<'info>( - ctx: Context<'_, '_, '_, 'info, UpdateCompressionConfig<'info>>, - instruction_data: Vec, - ) -> Result<()> { - let remaining = [ - ctx.accounts.config.to_account_info(), - ctx.accounts.update_authority.to_account_info(), - ]; - light_account::process_update_light_config( - &remaining, - &instruction_data, - &crate::LIGHT_CPI_SIGNER.program_id, - ).map_err(|e| anchor_lang::error::Error::from(solana_program_error::ProgramError::from(e)))?; - Ok(()) + // InitConfigParams struct - generated for both backends, but with different types + let init_config_params_struct = if backend.is_pinocchio() { + // Pinocchio: [u8; 32] instead of Pubkey + quote! { + #[derive(#serialize_derive, #deserialize_derive, Clone)] + pub struct InitConfigParams { + pub write_top_up: u32, + pub rent_sponsor: [u8; 32], + pub compression_authority: [u8; 32], + pub rent_config: #account_crate::rent::RentConfig, + pub address_space: Vec<[u8; 32]>, + } + } + } else { + // Anchor: Pubkey type + quote! { + /// Configuration parameters for initializing compression config. + /// Field order matches SDK client's `InitializeCompressionConfigAnchorData`. + #[derive(AnchorSerialize, AnchorDeserialize, Clone)] + pub struct InitConfigParams { + pub write_top_up: u32, + pub rent_sponsor: Pubkey, + pub compression_authority: Pubkey, + pub rent_config: #account_crate::RentConfig, + pub address_space: Vec, + } } }; @@ -569,7 +703,7 @@ pub(crate) fn generate_light_program_items( &pda_seeds, &token_seeds, &instruction_data, - false, // Anchor (not pinocchio) + backend.is_pinocchio(), )?; // Collect all generated items into a Vec @@ -590,23 +724,41 @@ pub(crate) fn generate_light_program_items( items.push(size_validation_checks); items.push(enum_and_traits); - items.push(quote! { #decompress_accounts }); - items.push(decompress_builder.generate_accounts_trait_impls()?); + + // Anchor-only: accounts structs, trait impls, processor module + if let Some(decompress_accounts) = decompress_accounts { + items.push(quote! { #decompress_accounts }); + items.push(decompress_builder.generate_accounts_trait_impls()?); + } if let Some(trait_impls) = trait_impls { items.push(quote! { #trait_impls }); } - items.push(quote! { #processor_module }); + if let Some(ref processor_module) = processor_module { + items.push(quote! { #processor_module }); + } if let Some(decompress_instruction) = decompress_instruction { items.push(quote! { #decompress_instruction }); } - items.push(quote! { #compress_accounts }); - items.push(compress_builder.generate_accounts_trait_impls()?); - items.push(quote! { #compress_instruction }); - items.push(quote! { #init_config_accounts }); - items.push(quote! { #update_config_accounts }); - items.push(quote! { #init_config_params_struct }); - items.push(quote! { #init_config_instruction }); - items.push(quote! { #update_config_instruction }); + if let Some(ref compress_accounts) = compress_accounts { + items.push(quote! { #compress_accounts }); + items.push(compress_builder.generate_accounts_trait_impls()?); + } + if let Some(ref compress_instruction) = compress_instruction { + items.push(quote! { #compress_instruction }); + } + if let Some(ref init_config_accounts) = init_config_accounts { + items.push(quote! { #init_config_accounts }); + } + if let Some(ref update_config_accounts) = update_config_accounts { + items.push(quote! { #update_config_accounts }); + } + items.push(init_config_params_struct); + if let Some(ref init_config_instruction) = init_config_instruction { + items.push(quote! { #init_config_instruction }); + } + if let Some(ref update_config_instruction) = update_config_instruction { + items.push(quote! { #update_config_instruction }); + } // PDA seed provider impls for pda_impl in pda_seed_provider_impls.into_iter() { @@ -622,22 +774,114 @@ pub(crate) fn generate_light_program_items( } } - // Error codes - items.push(error_codes); + // Error codes (Anchor only) + if let Some(error_codes) = error_codes { + items.push(error_codes); + } // Client functions (module + pub use statement) items.push(client_functions); // Generate enum dispatch methods for #[derive(LightProgram)] if let Some(enum_name) = enum_name { - // Compress dispatch: impl EnumName { pub fn compress_dispatch(...) } - if compress_builder.has_pdas() { - items.push(compress_builder.generate_enum_dispatch_method(enum_name)?); - } + if backend.is_pinocchio() { + // Pinocchio: enum associated functions for compress/decompress/config + if compress_builder.has_pdas() { + items.push( + compress_builder + .generate_enum_dispatch_method_with_backend(enum_name, backend)?, + ); + items.push( + compress_builder + .generate_enum_process_compress_with_backend(enum_name, backend)?, + ); + } - // Decompress dispatch: impl EnumName { pub fn decompress_dispatch(...) } - if !pda_ctx_seeds.is_empty() { - items.push(decompress_builder.generate_enum_decompress_dispatch(enum_name)?); + if !pda_ctx_seeds.is_empty() { + items.push( + decompress_builder + .generate_enum_process_decompress_with_backend(enum_name, backend)?, + ); + } + + // Config functions as enum associated methods (Pinocchio) + items.push(quote! { + impl #enum_name { + // SDK-standard discriminators (must match light-client) + pub const INITIALIZE_COMPRESSION_CONFIG: [u8; 8] = [133, 228, 12, 169, 56, 76, 222, 61]; + pub const UPDATE_COMPRESSION_CONFIG: [u8; 8] = [135, 215, 243, 81, 163, 146, 33, 70]; + pub const COMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [70, 236, 171, 120, 164, 93, 113, 181]; + pub const DECOMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [114, 67, 61, 123, 234, 31, 1, 112]; + + pub fn process_initialize_config( + accounts: &[pinocchio::account_info::AccountInfo], + data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + let params = ::try_from_slice(data) + .map_err(|_| pinocchio::program_error::ProgramError::BorshIoError)?; + + if accounts.len() < 5 { + return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); + } + + let fee_payer = &accounts[0]; + let config = &accounts[1]; + let _program_data = &accounts[2]; + let authority = &accounts[3]; + let system_program = &accounts[4]; + + #account_crate::process_initialize_light_config( + config, + authority, + ¶ms.rent_sponsor, + ¶ms.compression_authority, + params.rent_config, + params.write_top_up, + params.address_space, + 0, // config_bump + fee_payer, + system_program, + &crate::LIGHT_CPI_SIGNER.program_id, + ) + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + + pub fn process_update_config( + accounts: &[pinocchio::account_info::AccountInfo], + data: &[u8], + ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { + if accounts.len() < 2 { + return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); + } + + let config = &accounts[0]; + let authority = &accounts[1]; + + let remaining = [*config, *authority]; + #account_crate::process_update_light_config( + &remaining, + data, + &crate::LIGHT_CPI_SIGNER.program_id, + ) + .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) + } + } + }); + } else { + // Anchor: standard enum dispatch methods + if compress_builder.has_pdas() { + items.push( + compress_builder + .generate_enum_dispatch_method_with_backend(enum_name, backend)?, + ); + } + + if !pda_ctx_seeds.is_empty() { + items.push( + decompress_builder + .generate_enum_decompress_dispatch_with_backend(enum_name, backend)?, + ); + } } } @@ -958,10 +1202,9 @@ pub fn light_program_impl(_args: TokenStream, mut module: ItemMod) -> Result, ) -> Result> { - // Validate token seeds have seeds specified - if let Some(ref token_seed_specs) = token_seeds { - for spec in token_seed_specs { - if spec.seeds.is_empty() { - return Err(super::parsing::macro_error!( - &spec.variant, - "Token account '{}' must have seeds in #[account(seeds = [...])] for PDA signing.", - spec.variant - )); - } - } - } - - // Build PDA context seed info (same logic as Anchor version) - let pda_ctx_seeds: Vec = pda_seeds - .as_ref() - .map(|specs| { - specs - .iter() - .map(|spec| { - let ctx_fields = extract_ctx_seed_fields(&spec.seeds); - let inner_type = spec - .inner_type - .clone() - .unwrap_or_else(|| ident_to_type(&spec.variant)); - - let state_field_names: std::collections::HashSet = crate_ctx - .get_struct_fields(&inner_type) - .map(|fields| fields.into_iter().collect()) - .unwrap_or_default(); - - let params_only_seed_fields = - crate::light_pdas::seeds::get_params_only_seed_fields_from_spec( - spec, - &state_field_names, - ); - - let seed_count = spec.seeds.len() + 1; - - PdaCtxSeedInfo::with_state_fields( - spec.variant.clone(), - inner_type, - ctx_fields, - state_field_names, - params_only_seed_fields, - seed_count, - ) - }) - .collect() - }) - .unwrap_or_default(); - - let has_token_seeds_early = token_seeds.as_ref().map(|t| !t.is_empty()).unwrap_or(false); - - // Generate variant enum and traits using pinocchio builder - let enum_and_traits = if pda_ctx_seeds.is_empty() { - // Minimal placeholder for programs without PDA state accounts - quote! { - #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] - pub enum LightAccountVariant { - Empty, - } - - impl Default for LightAccountVariant { - fn default() -> Self { - Self::Empty - } - } - - impl light_account_pinocchio::hasher::DataHasher for LightAccountVariant { - fn hash(&self) -> std::result::Result<[u8; 32], light_account_pinocchio::hasher::HasherError> { - match self { - Self::Empty => Err(light_account_pinocchio::hasher::HasherError::EmptyInput), - } - } - } - - impl light_account_pinocchio::LightDiscriminator for LightAccountVariant { - const LIGHT_DISCRIMINATOR: [u8; 8] = [0; 8]; - const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; - } - - impl light_account_pinocchio::HasCompressionInfo for LightAccountVariant { - fn compression_info(&self) -> std::result::Result<&light_account_pinocchio::CompressionInfo, light_account_pinocchio::LightSdkTypesError> { - Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) - } - - fn compression_info_mut(&mut self) -> std::result::Result<&mut light_account_pinocchio::CompressionInfo, light_account_pinocchio::LightSdkTypesError> { - Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) - } - - fn compression_info_mut_opt(&mut self) -> &mut Option { - panic!("compression_info_mut_opt not supported for mint-only programs") - } - - fn set_compression_info_none(&mut self) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { - Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) - } - } - - impl light_account_pinocchio::Size for LightAccountVariant { - fn size(&self) -> std::result::Result { - Err(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData) - } - } - - #[cfg(not(target_os = "solana"))] - impl light_account_pinocchio::Pack for LightAccountVariant { - type Packed = Self; - fn pack(&self, _remaining_accounts: &mut light_account_pinocchio::interface::instruction::PackedAccounts) -> std::result::Result { - Ok(Self::Empty) - } - } - - impl light_account_pinocchio::Unpack for LightAccountVariant { - type Unpacked = Self; - fn unpack(&self, _remaining_accounts: &[AI]) -> std::result::Result { - Ok(Self::Empty) - } - } - - #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] - pub struct LightAccountData { - pub meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, - pub data: LightAccountVariant, - } - - impl Default for LightAccountData { - fn default() -> Self { - Self { - meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress::default(), - data: LightAccountVariant::default(), - } - } - } - } - } else { - let builder = LightVariantBuilder::new(&pda_ctx_seeds); - let builder = if let Some(ref token_seed_specs) = token_seeds { - if !token_seed_specs.is_empty() { - builder.with_token_seeds(token_seed_specs) - } else { - builder - } - } else { - builder - }; - builder.build_pinocchio()? - }; - - // Collect params-only seed fields for SeedParams struct - let mut all_params_only_fields: std::collections::BTreeMap = - std::collections::BTreeMap::new(); - for ctx_info in &pda_ctx_seeds { - for (field_name, field_type, _) in &ctx_info.params_only_seed_fields { - let field_str = field_name.to_string(); - all_params_only_fields - .entry(field_str) - .or_insert_with(|| field_type.clone()); - } - } - - // SeedParams with Borsh derives instead of Anchor derives - let seed_params_struct = if all_params_only_fields.is_empty() { - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug, Default)] - pub struct SeedParams; - } - } else { - let sorted_fields: Vec<_> = all_params_only_fields.iter().collect(); - let seed_param_fields: Vec<_> = sorted_fields - .iter() - .map(|(name, ty)| { - let field_ident = format_ident!("{}", name); - quote! { pub #field_ident: Option<#ty> } - }) - .collect(); - let seed_param_defaults: Vec<_> = sorted_fields - .iter() - .map(|(name, _)| { - let field_ident = format_ident!("{}", name); - quote! { #field_ident: None } - }) - .collect(); - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub struct SeedParams { - #(#seed_param_fields,)* - } - impl Default for SeedParams { - fn default() -> Self { - Self { - #(#seed_param_defaults,)* - } - } - } - } - }; - - // Seeds constructors with BorshDeserialize and light_account_pinocchio errors - let seeds_structs_and_constructors: Vec = if let Some(ref pda_seed_specs) = - pda_seeds - { - pda_seed_specs - .iter() - .zip(pda_ctx_seeds.iter()) - .map(|(spec, ctx_info)| { - let variant_name = &ctx_info.variant_name; - let inner_type = qualify_type_with_crate(&ctx_info.inner_type); - let seeds_struct_name = format_ident!("{}Seeds", variant_name); - let constructor_name = - format_ident!("{}", to_snake_case(&variant_name.to_string())); - let data_fields = extract_data_seed_fields(&spec.seeds); - - let data_verifications: Vec<_> = data_fields.iter().filter_map(|field| { - let field_str = field.to_string(); - if !ctx_info.state_field_names.contains(&field_str) { - return None; - } - Some(quote! { - if data.#field != seeds.#field { - return std::result::Result::Err( - light_account_pinocchio::LightSdkTypesError::InvalidInstructionData - ); - } - }) - }).collect(); - - // Pinocchio: use BorshDeserialize with light_account_pinocchio errors - let (deserialize_code, variant_data) = ( - quote! { - use borsh::BorshDeserialize; - let data: #inner_type = BorshDeserialize::deserialize(&mut &account_data[..]) - .map_err(|_| light_account_pinocchio::LightSdkTypesError::Borsh)?; - }, - quote! { data }, - ); - - quote! { - impl LightAccountVariant { - pub fn #constructor_name( - account_data: &[u8], - seeds: #seeds_struct_name, - ) -> std::result::Result { - #deserialize_code - - #(#data_verifications)* - - std::result::Result::Ok(Self::#variant_name { - seeds, - data: #variant_data, - }) - } - } - impl light_account_pinocchio::IntoVariant for #seeds_struct_name { - fn into_variant(self, data: &[u8]) -> std::result::Result { - LightAccountVariant::#constructor_name(data, self) - } - } - } - }) - .collect() - } else { - Vec::new() - }; - - let has_pda_seeds = pda_seeds.as_ref().map(|p| !p.is_empty()).unwrap_or(false); - let has_token_seeds = token_seeds.as_ref().map(|t| !t.is_empty()).unwrap_or(false); - - let instruction_variant = match (has_pda_seeds, has_token_seeds, has_mint_fields, has_ata_fields) - { - (true, true, _, _) => InstructionVariant::Mixed, - (true, false, _, _) => InstructionVariant::PdaOnly, - (false, true, _, _) => InstructionVariant::TokenOnly, - (false, false, true, _) => InstructionVariant::MintOnly, - (false, false, false, true) => InstructionVariant::AtaOnly, - (false, false, false, false) => { - return Err(syn::Error::new( - proc_macro2::Span::call_site(), - "No #[light_account(init)], #[light_account(init, mint::...)], #[light_account(init, associated_token::...)], or #[light_account(token::...)] fields found.\n\ - At least one light account field must be provided.", - )) - } - }; - - // Create builders for compress/decompress - let compress_builder = CompressBuilder::new(compressible_accounts.clone(), instruction_variant); - compress_builder.validate()?; - - let size_validation_checks = compress_builder.generate_size_validation_pinocchio()?; - - let decompress_builder = DecompressBuilder::new( - pda_ctx_seeds.clone(), - pda_seeds.clone(), - has_token_seeds_early, - ); - - // PDA seed provider impls (framework-agnostic, reused as-is) - let pda_seed_provider_impls = decompress_builder.generate_seed_provider_impls(true)?; - - // InitConfigParams with [u8; 32] instead of Pubkey - let init_config_params_struct = quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone)] - pub struct InitConfigParams { - pub write_top_up: u32, - pub rent_sponsor: [u8; 32], - pub compression_authority: [u8; 32], - pub rent_config: light_account_pinocchio::rent::RentConfig, - pub address_space: Vec<[u8; 32]>, - } - }; - - // Client functions (module + pub use - pinocchio uses light_account_pinocchio re-exports) - let client_functions = super::seed_codegen::generate_client_seed_functions( - &pda_seeds, - &token_seeds, - &instruction_data, - true, // Pinocchio - )?; - - // Collect all generated items - let mut items: Vec = Vec::new(); - - // SeedParams struct - items.push(seed_params_struct); - - // Seeds structs and constructors - for seeds_tokens in seeds_structs_and_constructors.into_iter() { - items.push(seeds_tokens); - } - - // PDA variant structs (already generated with pinocchio derives) - if !pda_variant_code.is_empty() { - items.push(pda_variant_code); - } - - // Size validation - items.push(size_validation_checks); - - // Variant enums and traits - items.push(enum_and_traits); - - // InitConfigParams - items.push(init_config_params_struct); - - // PDA seed provider impls - for pda_impl in pda_seed_provider_impls.into_iter() { - items.push(pda_impl); - } - - // CToken seed provider impls - if let Some(ref seeds) = token_seeds { - if !seeds.is_empty() { - let impl_code = - super::seed_codegen::generate_ctoken_seed_provider_implementation(seeds)?; - items.push(impl_code); - } - } - - // Client functions - items.push(client_functions); - - // Generate enum associated functions for pinocchio - if let Some(enum_name) = enum_name { - // Compress dispatch + process_compress - if compress_builder.has_pdas() { - items.push(compress_builder.generate_enum_dispatch_method_pinocchio(enum_name)?); - items.push(compress_builder.generate_enum_process_compress_pinocchio(enum_name)?); - } - - // Decompress dispatch + process_decompress - if !pda_ctx_seeds.is_empty() { - items.push(decompress_builder.generate_enum_process_decompress_pinocchio(enum_name)?); - } - - // Config functions as enum methods - items.push(quote! { - impl #enum_name { - // SDK-standard discriminators (must match light-client) - pub const INITIALIZE_COMPRESSION_CONFIG: [u8; 8] = [133, 228, 12, 169, 56, 76, 222, 61]; - pub const UPDATE_COMPRESSION_CONFIG: [u8; 8] = [135, 215, 243, 81, 163, 146, 33, 70]; - pub const COMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [70, 236, 171, 120, 164, 93, 113, 181]; - pub const DECOMPRESS_ACCOUNTS_IDEMPOTENT: [u8; 8] = [114, 67, 61, 123, 234, 31, 1, 112]; - - pub fn process_initialize_config( - accounts: &[pinocchio::account_info::AccountInfo], - data: &[u8], - ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { - let params = ::try_from_slice(data) - .map_err(|_| pinocchio::program_error::ProgramError::BorshIoError)?; - - if accounts.len() < 5 { - return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); - } - - let fee_payer = &accounts[0]; - let config = &accounts[1]; - let _program_data = &accounts[2]; - let authority = &accounts[3]; - let system_program = &accounts[4]; - - light_account_pinocchio::process_initialize_light_config( - config, - authority, - ¶ms.rent_sponsor, - ¶ms.compression_authority, - params.rent_config, - params.write_top_up, - params.address_space, - 0, // config_bump - fee_payer, - system_program, - &crate::LIGHT_CPI_SIGNER.program_id, - ) - .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) - } - - pub fn process_update_config( - accounts: &[pinocchio::account_info::AccountInfo], - data: &[u8], - ) -> std::result::Result<(), pinocchio::program_error::ProgramError> { - if accounts.len() < 2 { - return Err(pinocchio::program_error::ProgramError::NotEnoughAccountKeys); - } - - let authority = &accounts[0]; - let config = &accounts[1]; - - let remaining = [*config, *authority]; - light_account_pinocchio::process_update_light_config( - &remaining, - data, - &crate::LIGHT_CPI_SIGNER.program_id, - ) - .map_err(|e| pinocchio::program_error::ProgramError::Custom(u32::from(e))) - } - } - }); - } - - Ok(items) + generate_light_program_items_with_backend( + compressible_accounts, + pda_seeds, + token_seeds, + instruction_data, + crate_ctx, + has_mint_fields, + has_ata_fields, + pda_variant_code, + enum_name, + &PinocchioBackend, + ) } diff --git a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs index 79db49accd..3fad05c55c 100644 --- a/sdk-libs/macros/src/light_pdas/program/variant_enum.rs +++ b/sdk-libs/macros/src/light_pdas/program/variant_enum.rs @@ -15,7 +15,7 @@ use quote::{format_ident, quote}; use syn::{Ident, Result, Type}; use super::parsing::{SeedElement, TokenSeedSpec}; -use crate::light_pdas::shared_utils::qualify_type_with_crate; +use crate::light_pdas::{backend::CodegenBackend, shared_utils::qualify_type_with_crate}; // ============================================================================= // LIGHT VARIANT BUILDER @@ -71,20 +71,22 @@ impl<'a> LightVariantBuilder<'a> { Ok(()) } - /// Generate the complete enum definitions and trait implementations. - pub fn build(&self) -> Result { + /// Generate the complete enum definitions and trait implementations using the specified backend. + pub fn build_with_backend(&self, backend: &dyn CodegenBackend) -> Result { self.validate()?; // NOTE: Variant structs (`RecordVariant`, `PackedRecordVariant`, etc.) are generated // by `#[derive(LightAccounts)]` in the instruction module. We just wrap them in // the program-wide enum here. Do NOT regenerate them to avoid conflicts. - let token_seeds_structs = self.generate_token_seeds_structs(); - let token_variant_trait_impls = self.generate_token_variant_trait_impls(); - let unpacked_enum = self.generate_unpacked_enum(); - let packed_enum = self.generate_packed_enum(); - let light_account_data_struct = self.generate_light_account_data_struct(); - let decompress_variant_impl = self.generate_decompress_variant_impl(); - let pack_impl = self.generate_pack_impl(); + let token_seeds_structs = self.generate_token_seeds_structs_with_backend(backend); + let token_variant_trait_impls = + self.generate_token_variant_trait_impls_with_backend(backend); + let unpacked_enum = self.generate_unpacked_enum_with_backend(backend); + let packed_enum = self.generate_packed_enum_with_backend(backend); + let light_account_data_struct = + self.generate_light_account_data_struct_with_backend(backend); + let decompress_variant_impl = self.generate_decompress_variant_impl_with_backend(backend); + let pack_impl = self.generate_pack_impl_with_backend(backend); Ok(quote! { #token_seeds_structs @@ -97,43 +99,36 @@ impl<'a> LightVariantBuilder<'a> { }) } - /// Generate pinocchio-compatible enum definitions and trait implementations. - /// - /// Same as `build()` but uses: - /// - `BorshSerialize/BorshDeserialize` instead of `AnchorSerialize/AnchorDeserialize` - /// - `light_account_pinocchio::` instead of `light_account::` - /// - `pinocchio::account_info::AccountInfo` instead of anchor's AccountInfo - pub fn build_pinocchio(&self) -> Result { - self.validate()?; - - let token_seeds_structs = self.generate_token_seeds_structs_pinocchio(); - let token_variant_trait_impls = self.generate_token_variant_trait_impls_pinocchio(); - let unpacked_enum = self.generate_unpacked_enum_pinocchio(); - let packed_enum = self.generate_packed_enum_pinocchio(); - let light_account_data_struct = self.generate_light_account_data_struct_pinocchio(); - let decompress_variant_impl = self.generate_decompress_variant_impl_pinocchio(); - let pack_impl = self.generate_pack_impl_pinocchio(); - - Ok(quote! { - #token_seeds_structs - #token_variant_trait_impls - #unpacked_enum - #packed_enum - #light_account_data_struct - #decompress_variant_impl - #pack_impl - }) - } + // ========================================================================= + // UNIFIED BACKEND-BASED GENERATION METHODS + // ========================================================================= - /// Generate the `LightAccountData` wrapper struct. - fn generate_light_account_data_struct(&self) -> TokenStream { - quote! { - /// Wrapper for compressed account data with metadata. - /// Contains PACKED variant data that will be decompressed into PDA accounts. - #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] - pub struct LightAccountData { - pub meta: light_account::account_meta::CompressedAccountMetaNoLamportsNoAddress, - pub data: PackedLightAccountVariant, + /// Generate the `LightAccountData` wrapper struct using the specified backend. + fn generate_light_account_data_struct_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { + let account_crate = backend.account_crate(); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + + if backend.is_pinocchio() { + quote! { + #[derive(Clone, Debug, #serialize_derive, #deserialize_derive)] + pub struct LightAccountData { + pub meta: #account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub data: PackedLightAccountVariant, + } + } + } else { + quote! { + /// Wrapper for compressed account data with metadata. + /// Contains PACKED variant data that will be decompressed into PDA accounts. + #[derive(Clone, Debug, #serialize_derive, #deserialize_derive)] + pub struct LightAccountData { + pub meta: #account_crate::account_meta::CompressedAccountMetaNoLamportsNoAddress, + pub data: PackedLightAccountVariant, + } } } } @@ -143,8 +138,16 @@ impl<'a> LightVariantBuilder<'a> { // ========================================================================= /// Generate `{Variant}Seeds`, `Packed{Variant}Seeds`, and their Pack/Unpack impls - /// for each token variant. Same pattern as PDA seeds structs in accounts/variant.rs. - fn generate_token_seeds_structs(&self) -> TokenStream { + /// for each token variant using the specified backend. + fn generate_token_seeds_structs_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { + let account_crate = backend.account_crate(); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let sdk_error = backend.sdk_error_type(); + let structs: Vec<_> = self .token_seeds .iter() @@ -154,10 +157,16 @@ impl<'a> LightVariantBuilder<'a> { let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); let ctx_fields = extract_ctx_fields_from_token_spec(spec); - // Unpacked seeds: Pubkey fields + // Unpacked seeds: Pubkey or [u8; 32] fields depending on backend let unpacked_fields: Vec<_> = ctx_fields .iter() - .map(|f| quote! { pub #f: Pubkey }) + .map(|f| { + if backend.is_pinocchio() { + quote! { pub #f: [u8; 32] } + } else { + quote! { pub #f: Pubkey } + } + }) .collect(); // Packed seeds: u8 index fields + bump @@ -174,7 +183,11 @@ impl<'a> LightVariantBuilder<'a> { .iter() .map(|f| { let idx = format_ident!("{}_idx", f); - quote! { #idx: remaining_accounts.insert_or_get(AM::pubkey_from_bytes(self.#f.to_bytes())) } + if backend.is_pinocchio() { + quote! { #idx: remaining_accounts.insert_or_get(light_account_pinocchio::solana_pubkey::Pubkey::from(self.#f)) } + } else { + quote! { #idx: remaining_accounts.insert_or_get(AM::pubkey_from_bytes(self.#f.to_bytes())) } + } }) .collect(); @@ -185,18 +198,28 @@ impl<'a> LightVariantBuilder<'a> { .map(seed_to_unpacked_ref) .collect(); - // Unpack impl: u8 index -> Pubkey + // Unpack impl: u8 index -> Pubkey or [u8; 32] let unpack_resolve_stmts: Vec<_> = ctx_fields .iter() .map(|f| { let idx = format_ident!("{}_idx", f); - quote! { - let #f = solana_pubkey::Pubkey::new_from_array( - remaining_accounts - .get(self.#idx as usize) - .ok_or(light_account::LightSdkTypesError::InvalidInstructionData)? - .key() - ); + if backend.is_pinocchio() { + quote! { + let #f: [u8; 32] = + remaining_accounts + .get(self.#idx as usize) + .ok_or(#sdk_error::InvalidInstructionData)? + .key(); + } + } else { + quote! { + let #f = solana_pubkey::Pubkey::new_from_array( + remaining_accounts + .get(self.#idx as usize) + .ok_or(#sdk_error::InvalidInstructionData)? + .key() + ); + } } }) .collect(); @@ -205,62 +228,111 @@ impl<'a> LightVariantBuilder<'a> { let seeds_struct = if unpacked_fields.is_empty() { quote! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #seeds_name; } } else { quote! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #seeds_name { #(#unpacked_fields,)* } } }; + let pack_impl = if backend.is_pinocchio() { + quote! { + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack<#account_crate::solana_instruction::AccountMeta> for #seeds_name { + type Packed = #packed_seeds_name; + + fn pack( + &self, + remaining_accounts: &mut #account_crate::PackedAccounts, + ) -> std::result::Result { + let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; + let (_, __bump) = #account_crate::solana_pubkey::Pubkey::find_program_address( + __seeds, + &#account_crate::solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + Ok(#packed_seeds_name { + #(#pack_stmts,)* + bump: __bump, + }) + } + } + } + } else { + quote! { + // Pack trait is only available off-chain (client-side) + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack for #seeds_name { + type Packed = #packed_seeds_name; + + fn pack( + &self, + remaining_accounts: &mut #account_crate::interface::instruction::PackedAccounts, + ) -> std::result::Result { + let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; + let (_, __bump) = solana_pubkey::Pubkey::find_program_address( + __seeds, + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + Ok(#packed_seeds_name { + #(#pack_stmts,)* + bump: __bump, + }) + } + } + } + }; + + let unpack_impl = if backend.is_pinocchio() { + quote! { + impl #account_crate::Unpack for #packed_seeds_name { + type Unpacked = #seeds_name; + + fn unpack( + &self, + remaining_accounts: &[AI], + ) -> std::result::Result { + #(#unpack_resolve_stmts)* + Ok(#seeds_name { + #(#unpack_field_assigns,)* + }) + } + } + } + } else { + quote! { + impl #account_crate::Unpack for #packed_seeds_name { + type Unpacked = #seeds_name; + + fn unpack( + &self, + remaining_accounts: &[AI], + ) -> std::result::Result { + #(#unpack_resolve_stmts)* + Ok(#seeds_name { + #(#unpack_field_assigns,)* + }) + } + } + } + }; + quote! { #seeds_struct - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] pub struct #packed_seeds_name { #(#packed_fields,)* pub bump: u8, } - // Pack trait is only available off-chain (client-side) - #[cfg(not(target_os = "solana"))] - impl light_account::Pack for #seeds_name { - type Packed = #packed_seeds_name; - - fn pack( - &self, - remaining_accounts: &mut light_account::interface::instruction::PackedAccounts, - ) -> std::result::Result { - let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; - let (_, __bump) = solana_pubkey::Pubkey::find_program_address( - __seeds, - &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), - ); - Ok(#packed_seeds_name { - #(#pack_stmts,)* - bump: __bump, - }) - } - } - - impl light_account::Unpack for #packed_seeds_name { - type Unpacked = #seeds_name; - - fn unpack( - &self, - remaining_accounts: &[AI], - ) -> std::result::Result { - #(#unpack_resolve_stmts)* - Ok(#seeds_name { - #(#unpack_field_assigns,)* - }) - } - } + #pack_impl + #unpack_impl } }) .collect(); @@ -273,9 +345,15 @@ impl<'a> LightVariantBuilder<'a> { // ========================================================================= /// Generate `UnpackedTokenSeeds` and `PackedTokenSeeds` impls - /// on the local seed structs. The blanket impls in `light_account::token` - /// then provide `LightAccountVariantTrait` / `PackedLightAccountVariantTrait`. - fn generate_token_variant_trait_impls(&self) -> TokenStream { + /// on the local seed structs using the specified backend. + fn generate_token_variant_trait_impls_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { + let account_crate = backend.account_crate(); + let sdk_error = backend.sdk_error_type(); + let account_info_trait = backend.account_info_trait(); + let impls: Vec<_> = self .token_seeds .iter() @@ -334,7 +412,7 @@ impl<'a> LightVariantBuilder<'a> { let packed_seed_ref_items: Vec<_> = spec .seeds .iter() - .map(seed_to_packed_ref) + .map(|s| seed_to_packed_ref_with_crate(s, &account_crate)) .collect(); // --- Owner derivation from owner_seeds (constants only) --- @@ -354,12 +432,23 @@ impl<'a> LightVariantBuilder<'a> { } }) .collect(); - quote! { - let (__owner, _) = solana_pubkey::Pubkey::find_program_address( - &[#(#owner_seed_refs),*], - &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), - ); - __owner.to_bytes() + + if backend.is_pinocchio() { + quote! { + let (__owner, _) = #account_crate::solana_pubkey::Pubkey::find_program_address( + &[#(#owner_seed_refs),*], + &#account_crate::solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + __owner.to_bytes() + } + } else { + quote! { + let (__owner, _) = solana_pubkey::Pubkey::find_program_address( + &[#(#owner_seed_refs),*], + &solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), + ); + __owner.to_bytes() + } } } else { // No owner_seeds - return default (shouldn't happen for token accounts) @@ -367,7 +456,7 @@ impl<'a> LightVariantBuilder<'a> { }; quote! { - impl light_account::UnpackedTokenSeeds<#seed_count> + impl #account_crate::UnpackedTokenSeeds<#seed_count> for #seeds_name { type Packed = #packed_seeds_name; @@ -383,7 +472,7 @@ impl<'a> LightVariantBuilder<'a> { } } - impl light_account::PackedTokenSeeds<#seed_count> + impl #account_crate::PackedTokenSeeds<#seed_count> for #packed_seeds_name { type Unpacked = #seeds_name; @@ -392,18 +481,18 @@ impl<'a> LightVariantBuilder<'a> { self.bump } - fn unpack_seeds( + fn unpack_seeds( &self, accounts: &[AI], - ) -> std::result::Result { - >::unpack(self, accounts) + ) -> std::result::Result { + >::unpack(self, accounts) } - fn seed_refs_with_bump<'a, AI: light_account::AccountInfoTrait>( + fn seed_refs_with_bump<'a, AI: #account_info_trait>( &'a self, accounts: &'a [AI], bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], light_account::LightSdkTypesError> { + ) -> std::result::Result<[&'a [u8]; #seed_count], #sdk_error> { Ok([#(#packed_seed_ref_items,)* bump_storage]) } @@ -422,8 +511,12 @@ impl<'a> LightVariantBuilder<'a> { // ENUM GENERATION // ========================================================================= - /// Generate the unpacked `LightAccountVariant` enum. - fn generate_unpacked_enum(&self) -> TokenStream { + /// Generate the unpacked `LightAccountVariant` enum using the specified backend. + fn generate_unpacked_enum_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { + let account_crate = backend.account_crate(); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let pda_variants: Vec<_> = self .pda_ctx_seeds .iter() @@ -442,23 +535,37 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; let seeds_name = format_ident!("{}Seeds", variant_name); quote! { - #variant_name(light_account::token::TokenDataWithSeeds<#seeds_name>) + #variant_name(#account_crate::token::TokenDataWithSeeds<#seeds_name>) } }) .collect(); - quote! { - /// Program-wide unpacked variant enum collecting all per-field variants. - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub enum LightAccountVariant { - #(#pda_variants,)* - #(#token_variants,)* + if backend.is_pinocchio() { + quote! { + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] + pub enum LightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } + } + } else { + quote! { + /// Program-wide unpacked variant enum collecting all per-field variants. + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] + pub enum LightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } } } } - /// Generate the packed `PackedLightAccountVariant` enum. - fn generate_packed_enum(&self) -> TokenStream { + /// Generate the packed `PackedLightAccountVariant` enum using the specified backend. + fn generate_packed_enum_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { + let account_crate = backend.account_crate(); + let serialize_derive = backend.serialize_derive(); + let deserialize_derive = backend.deserialize_derive(); + let pda_variants: Vec<_> = self.pda_ctx_seeds .iter() @@ -484,17 +591,27 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); quote! { - #variant_name(light_account::token::TokenDataWithPackedSeeds<#packed_seeds_name>) + #variant_name(#account_crate::token::TokenDataWithPackedSeeds<#packed_seeds_name>) } }) .collect(); - quote! { - /// Program-wide packed variant enum for efficient serialization. - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize, Clone, Debug)] - pub enum PackedLightAccountVariant { - #(#pda_variants,)* - #(#token_variants,)* + if backend.is_pinocchio() { + quote! { + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] + pub enum PackedLightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } + } + } else { + quote! { + /// Program-wide packed variant enum for efficient serialization. + #[derive(#serialize_derive, #deserialize_derive, Clone, Debug)] + pub enum PackedLightAccountVariant { + #(#pda_variants,)* + #(#token_variants,)* + } } } } @@ -503,8 +620,15 @@ impl<'a> LightVariantBuilder<'a> { // DECOMPRESS VARIANT IMPL // ========================================================================= - /// Generate `impl DecompressVariant for PackedLightAccountVariant`. - fn generate_decompress_variant_impl(&self) -> TokenStream { + /// Generate `impl DecompressVariant for PackedLightAccountVariant` using the specified backend. + fn generate_decompress_variant_impl_with_backend( + &self, + backend: &dyn CodegenBackend, + ) -> TokenStream { + let account_crate = backend.account_crate(); + let account_info_type = backend.account_info_type(); + let sdk_error = backend.sdk_error_type(); + let pda_arms: Vec<_> = self .pda_ctx_seeds .iter() @@ -516,7 +640,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name { seeds, data } => { let packed_data = #packed_variant_type { seeds: seeds.clone(), data: data.clone() }; - light_account::prepare_account_for_decompression::<#seed_count, #packed_variant_type, light_account::AccountInfo<'info>>( + #account_crate::prepare_account_for_decompression::<#seed_count, #packed_variant_type, #account_info_type>( &packed_data, tree_info, output_queue_index, @@ -538,10 +662,10 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name(packed_data) => { - light_account::token::prepare_token_account_for_decompression::< + #account_crate::token::prepare_token_account_for_decompression::< #seed_count, - light_account::token::TokenDataWithPackedSeeds<#packed_seeds_name>, - light_account::AccountInfo<'info>, + #account_crate::token::TokenDataWithPackedSeeds<#packed_seeds_name>, + #account_info_type, >( packed_data, tree_info, @@ -554,18 +678,37 @@ impl<'a> LightVariantBuilder<'a> { }) .collect(); - quote! { - impl<'info> light_account::DecompressVariant> for PackedLightAccountVariant { - fn decompress( - &self, - tree_info: &light_account::PackedStateTreeInfo, - pda_account: &light_account::AccountInfo<'info>, - ctx: &mut light_account::DecompressCtx<'_, 'info>, - ) -> std::result::Result<(), light_account::LightSdkTypesError> { - let output_queue_index = ctx.output_queue_index; - match self { - #(#pda_arms)* - #(#token_arms)* + if backend.is_pinocchio() { + quote! { + impl #account_crate::DecompressVariant<#account_info_type> for PackedLightAccountVariant { + fn decompress( + &self, + tree_info: &#account_crate::PackedStateTreeInfo, + pda_account: &#account_info_type, + ctx: &mut #account_crate::DecompressCtx<'_>, + ) -> std::result::Result<(), #sdk_error> { + let output_queue_index = ctx.output_queue_index; + match self { + #(#pda_arms)* + #(#token_arms)* + } + } + } + } + } else { + quote! { + impl<'info> #account_crate::DecompressVariant<#account_crate::AccountInfo<'info>> for PackedLightAccountVariant { + fn decompress( + &self, + tree_info: &#account_crate::PackedStateTreeInfo, + pda_account: &#account_crate::AccountInfo<'info>, + ctx: &mut #account_crate::DecompressCtx<'_, 'info>, + ) -> std::result::Result<(), #sdk_error> { + let output_queue_index = ctx.output_queue_index; + match self { + #(#pda_arms)* + #(#token_arms)* + } } } } @@ -576,8 +719,13 @@ impl<'a> LightVariantBuilder<'a> { // PACK IMPL // ========================================================================= - /// Generate `impl light_account::Pack for LightAccountVariant`. - fn generate_pack_impl(&self) -> TokenStream { + /// Generate `impl Pack for LightAccountVariant` using the specified backend. + fn generate_pack_impl_with_backend(&self, backend: &dyn CodegenBackend) -> TokenStream { + let account_crate = backend.account_crate(); + let sdk_error = backend.sdk_error_type(); + let packed_accounts_type = backend.packed_accounts_type(); + let account_meta_type = backend.account_meta_type(); + let pda_arms: Vec<_> = self .pda_ctx_seeds .iter() @@ -588,7 +736,7 @@ impl<'a> LightVariantBuilder<'a> { quote! { Self::#variant_name { seeds, data } => { let variant = #variant_struct_name { seeds: seeds.clone(), data: data.clone() }; - let packed = light_account::Pack::pack(&variant, accounts)?; + let packed = #account_crate::Pack::pack(&variant, accounts)?; Ok(PackedLightAccountVariant::#variant_name { seeds: packed.seeds, data: packed.data }) } } @@ -602,490 +750,45 @@ impl<'a> LightVariantBuilder<'a> { let variant_name = &spec.variant; quote! { Self::#variant_name(data) => { - let packed = light_account::Pack::pack(data, accounts)?; + let packed = #account_crate::Pack::pack(data, accounts)?; Ok(PackedLightAccountVariant::#variant_name(packed)) } } }) .collect(); - quote! { - // Pack trait is only available off-chain (client-side) - #[cfg(not(target_os = "solana"))] - impl light_account::Pack for LightAccountVariant { - type Packed = PackedLightAccountVariant; - - fn pack( - &self, - accounts: &mut light_account::interface::instruction::PackedAccounts, - ) -> std::result::Result { - match self { - #(#pda_arms)* - #(#token_arms)* - } - } - } - } - } - - // ========================================================================= - // PINOCCHIO GENERATION METHODS - // ========================================================================= - - /// Generate token seeds structs (pinocchio version, uses BorshSerialize/BorshDeserialize). - fn generate_token_seeds_structs_pinocchio(&self) -> TokenStream { - let structs: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let variant_name = &spec.variant; - let seeds_name = format_ident!("{}Seeds", variant_name); - let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); - let ctx_fields = extract_ctx_fields_from_token_spec(spec); - - let unpacked_fields: Vec<_> = ctx_fields - .iter() - .map(|f| quote! { pub #f: [u8; 32] }) - .collect(); - - let packed_fields: Vec<_> = ctx_fields - .iter() - .map(|f| { - let idx = format_ident!("{}_idx", f); - quote! { pub #idx: u8 } - }) - .collect(); - - let pack_stmts: Vec<_> = ctx_fields - .iter() - .map(|f| { - let idx = format_ident!("{}_idx", f); - quote! { #idx: remaining_accounts.insert_or_get(light_account_pinocchio::solana_pubkey::Pubkey::from(self.#f)) } - }) - .collect(); - - let bump_seed_refs: Vec<_> = spec - .seeds - .iter() - .map(seed_to_unpacked_ref) - .collect(); - - let unpack_resolve_stmts: Vec<_> = ctx_fields - .iter() - .map(|f| { - let idx = format_ident!("{}_idx", f); - quote! { - let #f: [u8; 32] = - remaining_accounts - .get(self.#idx as usize) - .ok_or(light_account_pinocchio::LightSdkTypesError::InvalidInstructionData)? - .key(); - } - }) - .collect(); - - let unpack_field_assigns: Vec<_> = ctx_fields.iter().map(|f| quote! { #f }).collect(); - - let seeds_struct = if unpacked_fields.is_empty() { - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub struct #seeds_name; - } - } else { - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub struct #seeds_name { - #(#unpacked_fields,)* - } - } - }; - - quote! { - #seeds_struct - - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub struct #packed_seeds_name { - #(#packed_fields,)* - pub bump: u8, - } - - #[cfg(not(target_os = "solana"))] - impl light_account_pinocchio::Pack for #seeds_name { - type Packed = #packed_seeds_name; - - fn pack( - &self, - remaining_accounts: &mut light_account_pinocchio::PackedAccounts, - ) -> std::result::Result { - let __seeds: &[&[u8]] = &[#(#bump_seed_refs),*]; - let (_, __bump) = light_account_pinocchio::solana_pubkey::Pubkey::find_program_address( - __seeds, - &light_account_pinocchio::solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), - ); - Ok(#packed_seeds_name { - #(#pack_stmts,)* - bump: __bump, - }) - } - } - - impl light_account_pinocchio::Unpack for #packed_seeds_name { - type Unpacked = #seeds_name; - - fn unpack( - &self, - remaining_accounts: &[AI], - ) -> std::result::Result { - #(#unpack_resolve_stmts)* - Ok(#seeds_name { - #(#unpack_field_assigns,)* - }) - } - } - } - }) - .collect(); - - quote! { #(#structs)* } - } - - /// Generate token variant trait impls (pinocchio version). - fn generate_token_variant_trait_impls_pinocchio(&self) -> TokenStream { - let impls: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let seeds_name = format_ident!("{}Seeds", spec.variant); - let packed_seeds_name = format_ident!("Packed{}Seeds", spec.variant); - let seed_count = spec.seeds.len() + 1; - - let unpacked_seed_ref_items: Vec<_> = spec - .seeds - .iter() - .map(seed_to_unpacked_ref) - .collect(); - - let seed_vec_items: Vec<_> = spec - .seeds - .iter() - .map(|seed| { - match seed { - SeedElement::Literal(lit) => { - let value = lit.value(); - quote! { #value.as_bytes().to_vec() } - } - SeedElement::Expression(expr) => { - if let Some(field_name) = extract_ctx_field_from_expr(expr) { - quote! { self.#field_name.as_ref().to_vec() } - } else { - if let syn::Expr::Lit(lit_expr) = &**expr { - if let syn::Lit::ByteStr(byte_str) = &lit_expr.lit { - let bytes = byte_str.value(); - return quote! { vec![#(#bytes),*] }; - } - } - if let syn::Expr::Path(path_expr) = &**expr { - if path_expr.qself.is_none() { - if let Some(last_seg) = path_expr.path.segments.last() { - if crate::light_pdas::shared_utils::is_constant_identifier(&last_seg.ident.to_string()) { - let path = &path_expr.path; - return quote! { { let __seed: &[u8] = #path.as_ref(); __seed.to_vec() } }; - } - } - } - } - quote! { { let __seed: &[u8] = (#expr).as_ref(); __seed.to_vec() } } - } - } - } - }) - .collect(); - - let pinocchio_crate = quote! { light_account_pinocchio }; - let packed_seed_ref_items: Vec<_> = spec - .seeds - .iter() - .map(|s| seed_to_packed_ref_with_crate(s, &pinocchio_crate)) - .collect(); - - let owner_derivation = if let Some(owner_seeds) = &spec.owner_seeds { - let owner_seed_refs: Vec<_> = owner_seeds - .iter() - .map(|seed| { - match seed { - SeedElement::Literal(lit) => { - let value = lit.value(); - quote! { #value.as_bytes() } - } - SeedElement::Expression(expr) => { - quote! { { let __seed: &[u8] = (#expr).as_ref(); __seed } } - } - } - }) - .collect(); - quote! { - let (__owner, _) = light_account_pinocchio::solana_pubkey::Pubkey::find_program_address( - &[#(#owner_seed_refs),*], - &light_account_pinocchio::solana_pubkey::Pubkey::from(crate::LIGHT_CPI_SIGNER.program_id), - ); - __owner.to_bytes() - } - } else { - quote! { [0u8; 32] } - }; - - quote! { - impl light_account_pinocchio::UnpackedTokenSeeds<#seed_count> - for #seeds_name - { - type Packed = #packed_seeds_name; - - const PROGRAM_ID: [u8; 32] = crate::LIGHT_CPI_SIGNER.program_id; - - fn seed_vec(&self) -> Vec> { - vec![#(#seed_vec_items),*] - } - - fn seed_refs_with_bump<'a>(&'a self, bump_storage: &'a [u8; 1]) -> [&'a [u8]; #seed_count] { - [#(#unpacked_seed_ref_items,)* bump_storage] - } - } - - impl light_account_pinocchio::PackedTokenSeeds<#seed_count> - for #packed_seeds_name - { - type Unpacked = #seeds_name; - - fn bump(&self) -> u8 { - self.bump - } - - fn unpack_seeds( - &self, - accounts: &[AI], - ) -> std::result::Result { - >::unpack(self, accounts) - } - - fn seed_refs_with_bump<'a, AI: light_account_pinocchio::light_account_checks::AccountInfoTrait>( - &'a self, - accounts: &'a [AI], - bump_storage: &'a [u8; 1], - ) -> std::result::Result<[&'a [u8]; #seed_count], light_account_pinocchio::LightSdkTypesError> { - Ok([#(#packed_seed_ref_items,)* bump_storage]) - } - - fn derive_owner(&self) -> [u8; 32] { - #owner_derivation + if backend.is_pinocchio() { + quote! { + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack<#account_meta_type> for LightAccountVariant { + type Packed = PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut #packed_accounts_type, + ) -> std::result::Result { + match self { + #(#pda_arms)* + #(#token_arms)* } } } - }) - .collect(); - - quote! { #(#impls)* } - } - - /// Generate unpacked enum (pinocchio version). - fn generate_unpacked_enum_pinocchio(&self) -> TokenStream { - let pda_variants: Vec<_> = self - .pda_ctx_seeds - .iter() - .map(|info| { - let variant_name = &info.variant_name; - let seeds_type = format_ident!("{}Seeds", variant_name); - let inner_type = qualify_type_with_crate(&info.inner_type); - quote! { #variant_name { seeds: #seeds_type, data: #inner_type } } - }) - .collect(); - - let token_variants: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let variant_name = &spec.variant; - let seeds_name = format_ident!("{}Seeds", variant_name); - quote! { - #variant_name(light_account_pinocchio::token::TokenDataWithSeeds<#seeds_name>) - } - }) - .collect(); - - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub enum LightAccountVariant { - #(#pda_variants,)* - #(#token_variants,)* } - } - } - - /// Generate packed enum (pinocchio version). - fn generate_packed_enum_pinocchio(&self) -> TokenStream { - let pda_variants: Vec<_> = - self.pda_ctx_seeds - .iter() - .map(|info| { - let variant_name = &info.variant_name; - let packed_seeds_type = format_ident!("Packed{}Seeds", variant_name); - let inner_type = &info.inner_type; - let packed_data_type = - crate::light_pdas::shared_utils::make_packed_type(inner_type) - .unwrap_or_else(|| { - let type_str = quote!(#inner_type).to_string().replace(' ', ""); - let packed_name = format_ident!("Packed{}", type_str); - syn::parse_quote!(#packed_name) - }); - quote! { #variant_name { seeds: #packed_seeds_type, data: #packed_data_type } } - }) - .collect(); - - let token_variants: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let variant_name = &spec.variant; - let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); - quote! { - #variant_name(light_account_pinocchio::token::TokenDataWithPackedSeeds<#packed_seeds_name>) - } - }) - .collect(); - - quote! { - #[derive(borsh::BorshSerialize, borsh::BorshDeserialize, Clone, Debug)] - pub enum PackedLightAccountVariant { - #(#pda_variants,)* - #(#token_variants,)* - } - } - } - - /// Generate LightAccountData struct (pinocchio version). - fn generate_light_account_data_struct_pinocchio(&self) -> TokenStream { - quote! { - #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] - pub struct LightAccountData { - pub meta: light_account_pinocchio::account_meta::CompressedAccountMetaNoLamportsNoAddress, - pub data: PackedLightAccountVariant, - } - } - } - - /// Generate DecompressVariant impl (pinocchio version). - fn generate_decompress_variant_impl_pinocchio(&self) -> TokenStream { - let pda_arms: Vec<_> = self - .pda_ctx_seeds - .iter() - .map(|info| { - let variant_name = &info.variant_name; - let packed_variant_type = format_ident!("Packed{}Variant", variant_name); - let seed_count = info.seed_count; - - quote! { - Self::#variant_name { seeds, data } => { - let packed_data = #packed_variant_type { seeds: seeds.clone(), data: data.clone() }; - light_account_pinocchio::prepare_account_for_decompression::<#seed_count, #packed_variant_type, pinocchio::account_info::AccountInfo>( - &packed_data, - tree_info, - output_queue_index, - pda_account, - ctx, - ) - } - } - }) - .collect(); - - let token_arms: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let variant_name = &spec.variant; - let packed_seeds_name = format_ident!("Packed{}Seeds", variant_name); - let seed_count = spec.seeds.len() + 1; - - quote! { - Self::#variant_name(packed_data) => { - light_account_pinocchio::token::prepare_token_account_for_decompression::< - #seed_count, - light_account_pinocchio::token::TokenDataWithPackedSeeds<#packed_seeds_name>, - pinocchio::account_info::AccountInfo, - >( - packed_data, - tree_info, - output_queue_index, - pda_account, - ctx, - ) - } - } - }) - .collect(); - - quote! { - impl light_account_pinocchio::DecompressVariant for PackedLightAccountVariant { - fn decompress( - &self, - tree_info: &light_account_pinocchio::PackedStateTreeInfo, - pda_account: &pinocchio::account_info::AccountInfo, - ctx: &mut light_account_pinocchio::DecompressCtx<'_>, - ) -> std::result::Result<(), light_account_pinocchio::LightSdkTypesError> { - let output_queue_index = ctx.output_queue_index; - match self { - #(#pda_arms)* - #(#token_arms)* - } - } - } - } - } - - /// Generate Pack impl (pinocchio version). - fn generate_pack_impl_pinocchio(&self) -> TokenStream { - let pda_arms: Vec<_> = self - .pda_ctx_seeds - .iter() - .map(|info| { - let variant_name = &info.variant_name; - let variant_struct_name = format_ident!("{}Variant", variant_name); - - quote! { - Self::#variant_name { seeds, data } => { - let variant = #variant_struct_name { seeds: seeds.clone(), data: data.clone() }; - let packed = light_account_pinocchio::Pack::pack(&variant, accounts)?; - Ok(PackedLightAccountVariant::#variant_name { seeds: packed.seeds, data: packed.data }) - } - } - }) - .collect(); - - let token_arms: Vec<_> = self - .token_seeds - .iter() - .map(|spec| { - let variant_name = &spec.variant; - quote! { - Self::#variant_name(data) => { - let packed = light_account_pinocchio::Pack::pack(data, accounts)?; - Ok(PackedLightAccountVariant::#variant_name(packed)) - } - } - }) - .collect(); - - quote! { - #[cfg(not(target_os = "solana"))] - impl light_account_pinocchio::Pack for LightAccountVariant { - type Packed = PackedLightAccountVariant; - - fn pack( - &self, - accounts: &mut light_account_pinocchio::PackedAccounts, - ) -> std::result::Result { - match self { - #(#pda_arms)* - #(#token_arms)* + } else { + quote! { + // Pack trait is only available off-chain (client-side) + #[cfg(not(target_os = "solana"))] + impl #account_crate::Pack for LightAccountVariant { + type Packed = PackedLightAccountVariant; + + fn pack( + &self, + accounts: &mut #packed_accounts_type, + ) -> std::result::Result { + match self { + #(#pda_arms)* + #(#token_arms)* + } } } } @@ -1093,10 +796,6 @@ impl<'a> LightVariantBuilder<'a> { } } -// ============================================================================= -// PdaCtxSeedInfo -// ============================================================================= - /// Info about ctx.* seeds for a PDA type. #[derive(Clone, Debug)] pub struct PdaCtxSeedInfo { @@ -1259,9 +958,3 @@ fn seed_to_packed_ref_with_crate(seed: &SeedElement, account_crate: &TokenStream } } } - -/// Anchor-compatible wrapper. -fn seed_to_packed_ref(seed: &SeedElement) -> TokenStream { - let crate_path = quote! { light_account }; - seed_to_packed_ref_with_crate(seed, &crate_path) -}