@@ -23,9 +23,8 @@ pub const SLOTS_UNTIL_COMPRESSION: u64 = 100;
23
23
///
24
24
/// # Arguments
25
25
/// * `pda_account` - The PDA account to decompress into
26
- /// * `compressed_account_meta` - Optional metadata for the compressed account (None if PDA already exists)
27
- /// * `compressed_account_data` - The data to write to the PDA
28
- /// * `proof` - Optional validity proof (None if PDA already exists)
26
+ /// * `compressed_account` - The compressed account to decompress
27
+ /// * `proof` - Validity proof
29
28
/// * `cpi_accounts` - Accounts needed for CPI
30
29
/// * `owner_program` - The program that will own the PDA
31
30
/// * `rent_payer` - The account to pay for PDA rent
@@ -38,7 +37,7 @@ pub const SLOTS_UNTIL_COMPRESSION: u64 = 100;
38
37
/// * `Err(LightSdkError)` if there was an error
39
38
pub fn decompress_idempotent < ' info , A > (
40
39
pda_account : & AccountInfo < ' info > ,
41
- mut compressed_account : LightAccount < ' _ , A > ,
40
+ compressed_account : LightAccount < ' _ , A > ,
42
41
proof : ValidityProof ,
43
42
cpi_accounts : CpiAccounts < ' _ , ' info > ,
44
43
owner_program : & Pubkey ,
@@ -55,81 +54,147 @@ where
55
54
+ Clone
56
55
+ PdaTimingData ,
57
56
{
58
- // Check if PDA is already initialized
59
- if pda_account. data_len ( ) > 0 {
60
- msg ! ( "PDA already initialized, skipping decompression" ) ;
61
- return Ok ( ( ) ) ;
62
- }
57
+ decompress_multiple_idempotent (
58
+ & [ pda_account] ,
59
+ vec ! [ compressed_account] ,
60
+ & [ custom_seeds. to_vec ( ) ] ,
61
+ proof,
62
+ cpi_accounts,
63
+ owner_program,
64
+ rent_payer,
65
+ system_program,
66
+ )
67
+ }
63
68
64
- // Get compressed address
65
- let compressed_address = compressed_account
66
- . address ( )
67
- . ok_or ( LightSdkError :: ConstraintViolation ) ?;
69
+ /// Helper function to decompress multiple compressed accounts into PDAs idempotently.
70
+ ///
71
+ /// This function is idempotent, meaning it can be called multiple times with the same compressed accounts
72
+ /// and it will only decompress them once. If a PDA already exists and is initialized, it skips that account.
73
+ ///
74
+ /// # Arguments
75
+ /// * `decompress_inputs` - Vector of tuples containing (pda_account, compressed_account, custom_seeds, additional_seed)
76
+ /// * `proof` - Single validity proof for all accounts
77
+ /// * `cpi_accounts` - Accounts needed for CPI
78
+ /// * `owner_program` - The program that will own the PDAs
79
+ /// * `rent_payer` - The account to pay for PDA rent
80
+ /// * `system_program` - The system program
81
+ ///
82
+ /// # Returns
83
+ /// * `Ok(())` if all compressed accounts were decompressed successfully or PDAs already exist
84
+ /// * `Err(LightSdkError)` if there was an error
85
+ pub fn decompress_multiple_idempotent < ' info , A > (
86
+ pda_accounts : & [ & AccountInfo < ' info > ] ,
87
+ compressed_accounts : Vec < LightAccount < ' _ , A > > ,
88
+ custom_seeds_list : & [ Vec < & [ u8 ] > ] ,
89
+ proof : ValidityProof ,
90
+ cpi_accounts : CpiAccounts < ' _ , ' info > ,
91
+ owner_program : & Pubkey ,
92
+ rent_payer : & AccountInfo < ' info > ,
93
+ system_program : & AccountInfo < ' info > ,
94
+ ) -> Result < ( ) , LightSdkError >
95
+ where
96
+ A : DataHasher
97
+ + LightDiscriminator
98
+ + BorshSerialize
99
+ + BorshDeserialize
100
+ + Default
101
+ + Clone
102
+ + PdaTimingData ,
103
+ {
104
+ // Get current slot and rent once for all accounts
105
+ let clock = Clock :: get ( ) . map_err ( |_| LightSdkError :: Borsh ) ?;
106
+ let current_slot = clock. slot ;
107
+ let rent = Rent :: get ( ) . map_err ( |_| LightSdkError :: Borsh ) ?;
68
108
69
- // Derive onchain PDA
70
- // CHECK: PDA is derived from compressed account address.
71
- let mut seeds: Vec < & [ u8 ] > = custom_seeds. to_vec ( ) ;
72
- seeds. push ( & compressed_address) ;
109
+ // Calculate space needed for PDA (same for all accounts of type A)
110
+ let space = std:: mem:: size_of :: < A > ( ) + 8 ; // +8 for discriminator
111
+ let minimum_balance = rent. minimum_balance ( space) ;
73
112
74
- let ( pda_pubkey, pda_bump) = Pubkey :: find_program_address ( & seeds, owner_program) ; // TODO: consider passing the bump.
113
+ // Collect compressed accounts for CPI
114
+ let mut compressed_accounts_for_cpi = Vec :: new ( ) ;
115
+
116
+ for ( ( pda_account, mut compressed_account) , custom_seeds) in pda_accounts
117
+ . iter ( )
118
+ . zip ( compressed_accounts. into_iter ( ) )
119
+ . zip ( custom_seeds_list. iter ( ) )
120
+ . map ( |( ( pda, ca) , seeds) | ( ( pda, ca) , seeds. clone ( ) ) )
121
+ {
122
+ // Check if PDA is already initialized
123
+ if pda_account. data_len ( ) > 0 {
124
+ msg ! (
125
+ "PDA {} already initialized, skipping decompression" ,
126
+ pda_account. key
127
+ ) ;
128
+ continue ;
129
+ }
75
130
76
- // Verify PDA matches
77
- if pda_pubkey != * pda_account. key {
78
- msg ! ( "Invalid PDA pubkey" ) ;
79
- return Err ( LightSdkError :: ConstraintViolation ) ;
80
- }
131
+ // Get compressed address
132
+ let compressed_address = compressed_account
133
+ . address ( )
134
+ . ok_or ( LightSdkError :: ConstraintViolation ) ?;
81
135
82
- // Get current slot
83
- let clock = Clock :: get ( ) . map_err ( |_| LightSdkError :: Borsh ) ? ;
84
- let current_slot = clock . slot ;
136
+ // Derive onchain PDA
137
+ let mut seeds : Vec < & [ u8 ] > = custom_seeds ;
138
+ seeds . push ( & compressed_address ) ;
85
139
86
- // Calculate space needed for PDA
87
- let space = std:: mem:: size_of :: < A > ( ) + 8 ; // +8 for discriminator
140
+ let ( pda_pubkey, pda_bump) = Pubkey :: find_program_address ( & seeds, owner_program) ;
88
141
89
- // Get minimum rent
90
- let rent = Rent :: get ( ) . map_err ( |_| LightSdkError :: Borsh ) ?;
91
- let minimum_balance = rent. minimum_balance ( space) ;
142
+ // Verify PDA matches
143
+ if pda_pubkey != * pda_account. key {
144
+ msg ! ( "Invalid PDA pubkey for account {}" , pda_account. key) ;
145
+ return Err ( LightSdkError :: ConstraintViolation ) ;
146
+ }
92
147
93
- // Create PDA account
94
- let create_account_ix = system_instruction:: create_account (
95
- rent_payer. key ,
96
- pda_account. key ,
97
- minimum_balance,
98
- space as u64 ,
99
- owner_program,
100
- ) ;
101
-
102
- // Add bump to seeds for signing
103
- let bump_seed = [ pda_bump] ;
104
- let mut signer_seeds = seeds. clone ( ) ;
105
- signer_seeds. push ( & bump_seed) ;
106
- let signer_seeds_refs: Vec < & [ u8 ] > = signer_seeds. iter ( ) . map ( |s| * s) . collect ( ) ;
107
-
108
- invoke_signed (
109
- & create_account_ix,
110
- & [
111
- rent_payer. clone ( ) ,
112
- pda_account. clone ( ) ,
113
- system_program. clone ( ) ,
114
- ] ,
115
- & [ & signer_seeds_refs] ,
116
- ) ?;
117
-
118
- // Initialize PDA with decompressed data and update slot
119
- let mut decompressed_pda = compressed_account. account . clone ( ) ;
120
- decompressed_pda. set_last_written_slot ( current_slot) ;
121
- // Write data to PDA
122
- decompressed_pda
123
- . serialize ( & mut & mut pda_account. try_borrow_mut_data ( ) ?[ 8 ..] )
124
- . map_err ( |_| LightSdkError :: Borsh ) ?;
125
-
126
- // Zero the compressed account
127
- compressed_account. account = A :: default ( ) ;
128
-
129
- let cpi_inputs = CpiInputs :: new ( proof, vec ! [ compressed_account. to_account_info( ) ?] ) ;
130
- cpi_inputs. invoke_light_system_program ( cpi_accounts) ?;
131
-
132
- drop ( pda_account. try_borrow_mut_data ( ) ?) ; // todo: check if this is needed.
148
+ // Create PDA account
149
+ let create_account_ix = system_instruction:: create_account (
150
+ rent_payer. key ,
151
+ pda_account. key ,
152
+ minimum_balance,
153
+ space as u64 ,
154
+ owner_program,
155
+ ) ;
156
+
157
+ // Add bump to seeds for signing
158
+ let bump_seed = [ pda_bump] ;
159
+ let mut signer_seeds = seeds. clone ( ) ;
160
+ signer_seeds. push ( & bump_seed) ;
161
+ let signer_seeds_refs: Vec < & [ u8 ] > = signer_seeds. iter ( ) . map ( |s| * s) . collect ( ) ;
162
+
163
+ invoke_signed (
164
+ & create_account_ix,
165
+ & [
166
+ rent_payer. clone ( ) ,
167
+ ( * pda_account) . clone ( ) ,
168
+ system_program. clone ( ) ,
169
+ ] ,
170
+ & [ & signer_seeds_refs] ,
171
+ ) ?;
172
+
173
+ // Initialize PDA with decompressed data and update slot
174
+ let mut decompressed_pda = compressed_account. account . clone ( ) ;
175
+ decompressed_pda. set_last_written_slot ( current_slot) ;
176
+
177
+ // Write discriminator
178
+ let discriminator = A :: LIGHT_DISCRIMINATOR ;
179
+ pda_account. try_borrow_mut_data ( ) ?[ ..8 ] . copy_from_slice ( & discriminator) ;
180
+
181
+ // Write data to PDA
182
+ decompressed_pda
183
+ . serialize ( & mut & mut pda_account. try_borrow_mut_data ( ) ?[ 8 ..] )
184
+ . map_err ( |_| LightSdkError :: Borsh ) ?;
185
+
186
+ // Zero the compressed account
187
+ compressed_account. account = A :: default ( ) ;
188
+
189
+ // Add to CPI batch
190
+ compressed_accounts_for_cpi. push ( compressed_account. to_account_info ( ) ?) ;
191
+ }
192
+
193
+ // Make single CPI call with all compressed accounts
194
+ if !compressed_accounts_for_cpi. is_empty ( ) {
195
+ let cpi_inputs = CpiInputs :: new ( proof, compressed_accounts_for_cpi) ;
196
+ cpi_inputs. invoke_light_system_program ( cpi_accounts) ?;
197
+ }
133
198
134
199
Ok ( ( ) )
135
200
}
0 commit comments