1use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo;
3#[cfg(feature = "cpi-context")]
4use light_sdk_types::cpi_context_write::CpiContextWriteAccounts;
5use light_sdk_types::{
6 cpi_accounts::CpiAccountsConfig,
7 instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner,
8};
9use solana_account_info::AccountInfo;
10use solana_msg::msg;
11use solana_program_error::ProgramError;
12use solana_pubkey::Pubkey;
13
14use crate::{
15 cpi::{
16 v2::{CpiAccounts, LightSystemProgramCpi},
17 InvokeLightSystemProgram, LightCpiInstruction,
18 },
19 AnchorDeserialize, AnchorSerialize, LightDiscriminator,
20};
21
22pub trait HasTokenVariant {
24 fn is_packed_ctoken(&self) -> bool;
26}
27
28pub trait CTokenSeedProvider: Copy {
32 type Accounts<'info>;
34
35 fn get_seeds<'a, 'info>(
37 &self,
38 accounts: &'a Self::Accounts<'info>,
39 remaining_accounts: &'a [AccountInfo<'info>],
40 ) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
41
42 fn get_authority_seeds<'a, 'info>(
44 &self,
45 accounts: &'a Self::Accounts<'info>,
46 remaining_accounts: &'a [AccountInfo<'info>],
47 ) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
48}
49
50pub trait DecompressContext<'info> {
52 type CompressedData: HasTokenVariant;
54
55 type PackedTokenData;
57
58 type CompressedMeta: Clone;
60
61 type SeedParams;
63
64 fn fee_payer(&self) -> &AccountInfo<'info>;
66 fn config(&self) -> &AccountInfo<'info>;
67 fn rent_sponsor(&self) -> &AccountInfo<'info>;
68 fn ctoken_rent_sponsor(&self) -> Option<&AccountInfo<'info>>;
69 fn ctoken_program(&self) -> Option<&AccountInfo<'info>>;
70 fn ctoken_cpi_authority(&self) -> Option<&AccountInfo<'info>>;
71 fn ctoken_config(&self) -> Option<&AccountInfo<'info>>;
72
73 #[allow(clippy::type_complexity)]
77 #[allow(clippy::too_many_arguments)]
78 fn collect_pda_and_token<'b>(
79 &self,
80 cpi_accounts: &CpiAccounts<'b, 'info>,
81 address_space: Pubkey,
82 compressed_accounts: Vec<Self::CompressedData>,
83 solana_accounts: &[AccountInfo<'info>],
84 seed_params: Option<&Self::SeedParams>,
85 ) -> Result<(
86 Vec<light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo>,
87 Vec<(Self::PackedTokenData, Self::CompressedMeta)>
88 ), ProgramError>;
89
90 #[allow(clippy::too_many_arguments)]
94 fn process_tokens<'b>(
95 &self,
96 remaining_accounts: &[AccountInfo<'info>],
97 fee_payer: &AccountInfo<'info>,
98 ctoken_program: &AccountInfo<'info>,
99 ctoken_rent_sponsor: &AccountInfo<'info>,
100 ctoken_cpi_authority: &AccountInfo<'info>,
101 ctoken_config: &AccountInfo<'info>,
102 config: &AccountInfo<'info>,
103 ctoken_accounts: Vec<(Self::PackedTokenData, Self::CompressedMeta)>,
104 proof: crate::instruction::ValidityProof,
105 cpi_accounts: &CpiAccounts<'b, 'info>,
106 post_system_accounts: &[AccountInfo<'info>],
107 has_pdas: bool,
108 ) -> Result<(), ProgramError>;
109}
110
111pub trait PdaSeedDerivation<A, S> {
122 fn derive_pda_seeds_with_accounts(
123 &self,
124 program_id: &Pubkey,
125 accounts: &A,
126 seed_params: &S,
127 ) -> Result<(Vec<Vec<u8>>, Pubkey), ProgramError>;
128}
129
130#[inline(never)]
132pub fn check_account_types<T: HasTokenVariant>(compressed_accounts: &[T]) -> (bool, bool) {
133 let (mut has_tokens, mut has_pdas) = (false, false);
134 for account in compressed_accounts {
135 if account.is_packed_ctoken() {
136 has_tokens = true;
137 } else {
138 has_pdas = true;
139 }
140 if has_tokens && has_pdas {
141 break;
142 }
143 }
144 (has_tokens, has_pdas)
145}
146
147#[inline(never)]
149#[allow(clippy::too_many_arguments)]
150pub fn handle_packed_pda_variant<'a, 'b, 'info, T, P, A, S>(
151 accounts_rent_sponsor: &AccountInfo<'info>,
152 cpi_accounts: &CpiAccounts<'b, 'info>,
153 address_space: Pubkey,
154 solana_account: &AccountInfo<'info>,
155 index: usize,
156 packed: &P,
157 meta: &CompressedAccountMetaNoLamportsNoAddress,
158 post_system_accounts: &[AccountInfo<'info>],
159 compressed_pda_infos: &mut Vec<CompressedAccountInfo>,
160 program_id: &Pubkey,
161 seed_accounts: &A,
162 seed_params: Option<&S>,
163) -> Result<(), ProgramError>
164where
165 T: PdaSeedDerivation<A, S>
166 + Clone
167 + crate::account::Size
168 + LightDiscriminator
169 + Default
170 + AnchorSerialize
171 + AnchorDeserialize
172 + crate::compressible::HasCompressionInfo
173 + 'info,
174 P: crate::compressible::Unpack<Unpacked = T>,
175 S: Default,
176{
177 let data: T = P::unpack(packed, post_system_accounts)?;
178
179 let (seeds_vec, derived_pda) = if let Some(params) = seed_params {
183 data.derive_pda_seeds_with_accounts(program_id, seed_accounts, params)?
184 } else {
185 let default_params = S::default();
186 data.derive_pda_seeds_with_accounts(program_id, seed_accounts, &default_params)?
187 };
188 if derived_pda != *solana_account.key {
189 msg!(
190 "Derived PDA does not match account at index {}: expected {:?}, got {:?}, seeds: {:?}",
191 index,
192 solana_account.key,
193 derived_pda,
194 seeds_vec
195 );
196 return Err(ProgramError::from(
197 crate::error::LightSdkError::ConstraintViolation,
198 ));
199 }
200
201 let compressed_infos = {
203 let seed_refs: Vec<&[u8]> = seeds_vec.iter().map(|v| v.as_slice()).collect();
204 crate::compressible::decompress_idempotent::prepare_account_for_decompression_idempotent::<T>(
205 program_id,
206 data,
207 crate::compressible::decompress_idempotent::into_compressed_meta_with_address(
208 meta,
209 solana_account,
210 address_space,
211 program_id,
212 ),
213 solana_account,
214 accounts_rent_sponsor,
215 cpi_accounts,
216 seed_refs.as_slice(),
217 )?
218 };
219 compressed_pda_infos.extend(compressed_infos);
220 Ok(())
221}
222
223#[inline(never)]
225#[allow(clippy::too_many_arguments)]
226pub fn process_decompress_accounts_idempotent<'info, Ctx>(
227 ctx: &Ctx,
228 remaining_accounts: &[AccountInfo<'info>],
229 compressed_accounts: Vec<Ctx::CompressedData>,
230 proof: crate::instruction::ValidityProof,
231 system_accounts_offset: u8,
232 cpi_signer: CpiSigner,
233 program_id: &Pubkey,
234 seed_params: Option<&Ctx::SeedParams>,
235) -> Result<(), ProgramError>
236where
237 Ctx: DecompressContext<'info>,
238{
239 let compression_config =
240 crate::compressible::CompressibleConfig::load_checked(ctx.config(), program_id)?;
241 let address_space = compression_config.address_space[0];
242
243 let (has_tokens, has_pdas) = check_account_types(&compressed_accounts);
244 if !has_tokens && !has_pdas {
245 return Ok(());
246 }
247
248 let system_accounts_offset_usize = system_accounts_offset as usize;
249 if system_accounts_offset_usize > remaining_accounts.len() {
250 return Err(ProgramError::NotEnoughAccountKeys);
251 }
252
253 let cpi_accounts = if has_tokens {
254 CpiAccounts::new_with_config(
255 ctx.fee_payer(),
256 &remaining_accounts[system_accounts_offset_usize..],
257 CpiAccountsConfig::new_with_cpi_context(cpi_signer),
258 )
259 } else {
260 CpiAccounts::new(
261 ctx.fee_payer(),
262 &remaining_accounts[system_accounts_offset_usize..],
263 cpi_signer,
264 )
265 };
266
267 let pda_accounts_start = remaining_accounts
268 .len()
269 .checked_sub(compressed_accounts.len())
270 .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
271 let solana_accounts = remaining_accounts
272 .get(pda_accounts_start..)
273 .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
274 let post_system_offset = cpi_accounts.system_accounts_end_offset();
275 let all_infos = cpi_accounts.account_infos();
276 let post_system_accounts = all_infos
277 .get(post_system_offset..)
278 .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
279
280 let (compressed_pda_infos, compressed_token_accounts) = ctx.collect_pda_and_token(
282 &cpi_accounts,
283 address_space,
284 compressed_accounts,
285 solana_accounts,
286 seed_params,
287 )?;
288
289 let has_pdas = !compressed_pda_infos.is_empty();
290 let has_tokens = !compressed_token_accounts.is_empty();
291 if !has_pdas && !has_tokens {
292 return Ok(());
293 }
294
295 let fee_payer = ctx.fee_payer();
296
297 #[cfg(feature = "cpi-context")]
299 if has_pdas && has_tokens {
300 let authority = cpi_accounts
301 .authority()
302 .map_err(|_| ProgramError::MissingRequiredSignature)?;
303 let cpi_context = cpi_accounts
304 .cpi_context()
305 .map_err(|_| ProgramError::MissingRequiredSignature)?;
306 let system_cpi_accounts = CpiContextWriteAccounts {
307 fee_payer,
308 authority,
309 cpi_context,
310 cpi_signer,
311 };
312
313 LightSystemProgramCpi::new_cpi(cpi_signer, proof)
314 .with_account_infos(&compressed_pda_infos)
315 .write_to_cpi_context_first()
316 .invoke_write_to_cpi_context_first(system_cpi_accounts)?;
317 } else if has_pdas {
318 LightSystemProgramCpi::new_cpi(cpi_accounts.config().cpi_signer, proof)
319 .with_account_infos(&compressed_pda_infos)
320 .invoke(cpi_accounts.clone())?;
321 }
322
323 #[cfg(not(feature = "cpi-context"))]
325 if has_pdas {
326 LightSystemProgramCpi::new_cpi(cpi_accounts.config().cpi_signer, proof)
327 .with_account_infos(&compressed_pda_infos)
328 .invoke(cpi_accounts.clone())?;
329 }
330
331 if has_tokens {
333 let ctoken_program = ctx
334 .ctoken_program()
335 .ok_or(ProgramError::NotEnoughAccountKeys)?;
336 let ctoken_rent_sponsor = ctx
337 .ctoken_rent_sponsor()
338 .ok_or(ProgramError::NotEnoughAccountKeys)?;
339 let ctoken_cpi_authority = ctx
340 .ctoken_cpi_authority()
341 .ok_or(ProgramError::NotEnoughAccountKeys)?;
342 let ctoken_config = ctx
343 .ctoken_config()
344 .ok_or(ProgramError::NotEnoughAccountKeys)?;
345
346 ctx.process_tokens(
347 remaining_accounts,
348 fee_payer,
349 ctoken_program,
350 ctoken_rent_sponsor,
351 ctoken_cpi_authority,
352 ctoken_config,
353 ctx.config(),
354 compressed_token_accounts,
355 proof,
356 &cpi_accounts,
357 post_system_accounts,
358 has_pdas,
359 )?;
360 }
361
362 Ok(())
363}