11//! DecompressContext trait generation.
2+ //!
3+ //! Generates the implementation of the DecompressContext trait for the
4+ //! DecompressAccountsIdempotent struct. This uses a zero-allocation two-pass approach:
5+ //! - Pass 1 (collect_layout_and_tokens): Count PDAs, collect output_data_lens, collect tokens
6+ //! - Pass 2 (create_and_write_pda): Create PDA on Solana, return data for zero-copy buffer
27
38use proc_macro2:: TokenStream ;
49use quote:: { format_ident, quote} ;
@@ -15,23 +20,40 @@ pub fn generate_decompress_context_trait_impl(
1520 token_variant_ident : Ident ,
1621 lifetime : syn:: Lifetime ,
1722) -> Result < TokenStream > {
18- // Generate match arms that extract idx fields, resolve Pubkeys, construct CtxSeeds
19- let pda_match_arms: Vec < _ > = pda_ctx_seeds
23+ // Generate match arms for collect_layout_and_tokens - count PDAs that need decompression
24+ let collect_layout_pda_arms: Vec < _ > = pda_ctx_seeds
25+ . iter ( )
26+ . map ( |info| {
27+ let variant_name = & info. variant_name ;
28+ let packed_variant_name = make_packed_variant_name ( variant_name) ;
29+ quote ! {
30+ LightAccountVariant :: #packed_variant_name { .. } => {
31+ // PDA variant: only count if not already initialized (idempotent check)
32+ if solana_accounts[ i] . data_is_empty( ) {
33+ pda_indices[ pda_count] = i;
34+ pda_count += 1 ;
35+ }
36+ }
37+ LightAccountVariant :: #variant_name { .. } => {
38+ return std:: result:: Result :: Err ( light_sdk:: error:: LightSdkError :: UnexpectedUnpackedVariant . into( ) ) ;
39+ }
40+ }
41+ } )
42+ . collect ( ) ;
43+
44+ // Generate match arms for create_and_write_pda - unpack, derive seeds, create PDA, return data
45+ let create_pda_match_arms: Vec < _ > = pda_ctx_seeds
2046 . iter ( )
2147 . map ( |info| {
22- // Use variant_name for enum variant matching
2348 let variant_name = & info. variant_name ;
24- // Use inner_type for type references (generics, trait bounds)
25- // Qualify with crate:: to ensure it's accessible from generated code
2649 let inner_type = qualify_type_with_crate ( & info. inner_type ) ;
2750 let packed_variant_name = make_packed_variant_name ( variant_name) ;
28- // Create packed type (also qualified with crate::)
2951 let packed_inner_type = make_packed_type ( & info. inner_type )
3052 . expect ( "inner_type should be a valid type path" ) ;
31- // Use variant_name for CtxSeeds struct (matches what decompress.rs generates)
3253 let ctx_seeds_struct_name = format_ident ! ( "{}CtxSeeds" , variant_name) ;
3354 let ctx_fields = & info. ctx_seed_fields ;
3455 let params_only_fields = & info. params_only_seed_fields ;
56+
3557 // Generate pattern to extract idx fields from packed variant
3658 let idx_field_patterns: Vec < _ > = ctx_fields. iter ( ) . map ( |field| {
3759 let idx_field = format_ident ! ( "{}_idx" , field) ;
@@ -42,11 +64,12 @@ pub fn generate_decompress_context_trait_impl(
4264 quote ! { #field }
4365 } ) . collect ( ) ;
4466 // Generate code to resolve idx fields to Pubkeys
67+ // Note: when matching on &compressed_data.data, idx fields are references, so we dereference
4568 let resolve_ctx_seeds: Vec < _ > = ctx_fields. iter ( ) . map ( |field| {
4669 let idx_field = format_ident ! ( "{}_idx" , field) ;
4770 quote ! {
4871 let #field = * post_system_accounts
49- . get( #idx_field as usize )
72+ . get( * #idx_field as usize )
5073 . ok_or( solana_program_error:: ProgramError :: InvalidAccountData ) ?
5174 . key;
5275 }
@@ -61,38 +84,40 @@ pub fn generate_decompress_context_trait_impl(
6184 quote ! { let ctx_seeds = #ctx_seeds_struct_name { #( #field_inits) , * } ; }
6285 } ;
6386 // Generate SeedParams update with params-only field values
64- // Note: variant_seed_params is declared OUTSIDE the match to avoid borrow checker issues
65- // (the reference passed to handle_packed_pda_variant would outlive the match arm scope)
66- // params-only fields are stored directly in packed variant (not by reference),
67- // so we use the value directly without dereferencing
87+ // Note: when matching on &compressed_data.data, params fields are references, so we dereference
6888 let seed_params_update = if params_only_fields. is_empty ( ) {
69- // No update needed - use the default value declared before match
7089 quote ! { }
7190 } else {
7291 let field_inits: Vec < _ > = params_only_fields. iter ( ) . map ( |( field, _, _) | {
73- quote ! { #field: std:: option:: Option :: Some ( #field) }
92+ quote ! { #field: std:: option:: Option :: Some ( * #field) }
7493 } ) . collect ( ) ;
7594 quote ! { variant_seed_params = SeedParams { #( #field_inits, ) * ..Default :: default ( ) } ; }
7695 } ;
96+
7797 quote ! {
7898 LightAccountVariant :: #packed_variant_name { data: packed, #( #idx_field_patterns, ) * #( #params_field_patterns, ) * .. } => {
7999 #( #resolve_ctx_seeds) *
80100 #ctx_seeds_construction
81101 #seed_params_update
82- light_sdk:: interface:: handle_packed_pda_variant:: <#inner_type, #packed_inner_type, _, _>(
83- & * self . rent_sponsor,
84- cpi_accounts,
85- address_space,
86- & solana_accounts[ i] ,
87- i,
88- & packed,
89- & meta,
90- post_system_accounts,
91- & mut compressed_pda_infos,
102+
103+ // Unpack the data
104+ let data: #inner_type = <#packed_inner_type as light_sdk:: interface:: Unpack >:: unpack( & packed, post_system_accounts) ?;
105+
106+ // Use helper function to derive seeds, verify PDA, create account, and write to zero-copy buffer
107+ // Pass data and compressed_meta by reference to reduce caller stack usage
108+ light_sdk:: interface:: derive_verify_create_and_write_pda:: <#inner_type, _, _>(
92109 & program_id,
110+ & data,
93111 & ctx_seeds,
94- std:: option:: Option :: Some ( & variant_seed_params) ,
95- ) ?;
112+ seed_params,
113+ & variant_seed_params,
114+ compressed_meta,
115+ address_space,
116+ solana_account,
117+ & * self . rent_sponsor,
118+ cpi_accounts,
119+ zc_info,
120+ )
96121 }
97122 LightAccountVariant :: #variant_name { .. } => {
98123 return std:: result:: Result :: Err ( light_sdk:: error:: LightSdkError :: UnexpectedUnpackedVariant . into( ) ) ;
@@ -102,9 +127,18 @@ pub fn generate_decompress_context_trait_impl(
102127 . collect ( ) ;
103128
104129 // For mint-only programs (no PDA variants), add an arm for the Empty variant
105- let empty_variant_arm = if pda_ctx_seeds. is_empty ( ) {
130+ let empty_variant_arm_collect = if pda_ctx_seeds. is_empty ( ) {
131+ quote ! {
132+ LightAccountVariant :: Empty => {
133+ return std:: result:: Result :: Err ( solana_program_error:: ProgramError :: InvalidAccountData ) ;
134+ }
135+ }
136+ } else {
137+ quote ! { }
138+ } ;
139+
140+ let empty_variant_arm_create = if pda_ctx_seeds. is_empty ( ) {
106141 quote ! {
107- // Mint-only programs have an Empty variant that should never be decompressed
108142 LightAccountVariant :: Empty => {
109143 return std:: result:: Result :: Err ( solana_program_error:: ProgramError :: InvalidAccountData ) ;
110144 }
@@ -150,51 +184,65 @@ pub fn generate_decompress_context_trait_impl(
150184 self . ctoken_config. as_ref( ) . map( |a| & * * a)
151185 }
152186
153- fn collect_pda_and_token<' b>(
187+ #[ allow( clippy:: type_complexity) ]
188+ fn collect_layout_and_tokens(
154189 & self ,
155- cpi_accounts: & light_sdk:: cpi:: v2:: CpiAccounts <' b, #lifetime>,
156- address_space: solana_pubkey:: Pubkey ,
157- compressed_accounts: Vec <Self :: CompressedData >,
190+ compressed_accounts: & [ Self :: CompressedData ] ,
158191 solana_accounts: & [ solana_account_info:: AccountInfo <#lifetime>] ,
159- seed_params: std:: option:: Option <& Self :: SeedParams >,
160- ) -> std:: result:: Result <(
161- Vec <:: light_sdk:: compressed_account:: CompressedAccountInfo >,
162- Vec <( Self :: PackedTokenData , Self :: CompressedMeta ) >,
163- ) , solana_program_error:: ProgramError > {
164- solana_msg:: msg!( "collect_pda_and_token: start, {} accounts" , compressed_accounts. len( ) ) ;
165- let post_system_offset = cpi_accounts. system_accounts_end_offset( ) ;
166- let all_infos = cpi_accounts. account_infos( ) ;
167- let post_system_accounts = & all_infos[ post_system_offset..] ;
168- let program_id = & crate :: ID ;
169-
170- solana_msg:: msg!( "collect_pda_and_token: allocating vecs" ) ;
171- let mut compressed_pda_infos = Vec :: with_capacity( compressed_accounts. len( ) ) ;
192+ pda_indices: & mut [ usize ; light_sdk:: interface:: MAX_DECOMPRESS_ACCOUNTS ] ,
193+ ) -> std:: result:: Result <( usize , Vec <( Self :: PackedTokenData , Self :: CompressedMeta ) >) , solana_program_error:: ProgramError > {
194+ let mut pda_count: usize = 0 ;
172195 let mut compressed_token_accounts = Vec :: with_capacity( compressed_accounts. len( ) ) ;
173196
174- solana_msg:: msg!( "collect_pda_and_token: starting loop" ) ;
175- for ( i, compressed_data) in compressed_accounts. into_iter( ) . enumerate( ) {
176- solana_msg:: msg!( "collect_pda_and_token: processing account {}" , i) ;
177- let meta = compressed_data. meta;
178- // Declare variant_seed_params OUTSIDE the match to avoid borrow checker issues
179- // (reference passed to handle_packed_pda_variant with ? would outlive match arm scope)
180- let mut variant_seed_params = SeedParams :: default ( ) ;
181- match compressed_data. data {
182- #( #pda_match_arms) *
183- LightAccountVariant :: PackedCTokenData ( mut data) => {
184- solana_msg:: msg!( "collect_pda_and_token: token variant {}" , i) ;
185- data. token_data. version = 3 ;
186- compressed_token_accounts. push( ( data, meta) ) ;
187- solana_msg:: msg!( "collect_pda_and_token: token {} done" , i) ;
197+ for ( i, compressed_data) in compressed_accounts. iter( ) . enumerate( ) {
198+ let meta = compressed_data. meta. clone( ) ;
199+ match & compressed_data. data {
200+ #( #collect_layout_pda_arms) *
201+ LightAccountVariant :: PackedCTokenData ( data) => {
202+ let mut token_data = data. clone( ) ;
203+ token_data. token_data. version = 3 ;
204+ compressed_token_accounts. push( ( token_data, meta) ) ;
188205 }
189206 LightAccountVariant :: CTokenData ( _) => {
190207 return std:: result:: Result :: Err ( light_sdk:: error:: LightSdkError :: UnexpectedUnpackedVariant . into( ) ) ;
191208 }
192- #empty_variant_arm
209+ #empty_variant_arm_collect
193210 }
194211 }
195212
196- solana_msg:: msg!( "collect_pda_and_token: loop done, pdas={} tokens={}" , compressed_pda_infos. len( ) , compressed_token_accounts. len( ) ) ;
197- std:: result:: Result :: Ok ( ( compressed_pda_infos, compressed_token_accounts) )
213+ std:: result:: Result :: Ok ( ( pda_count, compressed_token_accounts) )
214+ }
215+
216+ #[ inline( never) ]
217+ #[ allow( clippy:: too_many_arguments) ]
218+ fn create_and_write_pda<' b, ' c>(
219+ & self ,
220+ cpi_accounts: & light_sdk:: cpi:: v2:: CpiAccounts <' b, #lifetime>,
221+ address_space: & solana_pubkey:: Pubkey ,
222+ compressed_data: & Self :: CompressedData ,
223+ solana_account: & solana_account_info:: AccountInfo <#lifetime>,
224+ seed_params: std:: option:: Option <& Self :: SeedParams >,
225+ zc_info: & mut light_sdk:: interface:: ZCompressedAccountInfoMut <' c>,
226+ ) -> std:: result:: Result <bool , solana_program_error:: ProgramError > {
227+ let post_system_offset = cpi_accounts. system_accounts_end_offset( ) ;
228+ let all_infos = cpi_accounts. account_infos( ) ;
229+ let post_system_accounts = & all_infos[ post_system_offset..] ;
230+ let program_id = crate :: ID ;
231+ let compressed_meta = & compressed_data. meta;
232+ let mut variant_seed_params = SeedParams :: default ( ) ;
233+ let _ = & variant_seed_params; // Suppress unused warning when no params-only fields
234+
235+ match & compressed_data. data {
236+ #( #create_pda_match_arms) *
237+ LightAccountVariant :: PackedCTokenData ( _) => {
238+ // Tokens are handled separately, skip here
239+ std:: result:: Result :: Ok ( false )
240+ }
241+ LightAccountVariant :: CTokenData ( _) => {
242+ return std:: result:: Result :: Err ( light_sdk:: error:: LightSdkError :: UnexpectedUnpackedVariant . into( ) ) ;
243+ }
244+ #empty_variant_arm_create
245+ }
198246 }
199247
200248 #[ inline( never) ]
0 commit comments