light_sdk/compressible/
compress_runtime.rs

1//! Runtime for compress_accounts_idempotent instruction.
2use light_compressed_account::instruction_data::with_account_info::CompressedAccountInfo;
3use light_sdk_types::{
4    instruction::account_meta::CompressedAccountMetaNoLamportsNoAddress, CpiSigner,
5};
6use solana_account_info::AccountInfo;
7use solana_program_error::ProgramError;
8use solana_pubkey::Pubkey;
9
10pub trait CompressContext<'info> {
11    fn fee_payer(&self) -> &AccountInfo<'info>;
12    fn config(&self) -> &AccountInfo<'info>;
13    fn rent_sponsor(&self) -> &AccountInfo<'info>;
14    fn compression_authority(&self) -> &AccountInfo<'info>;
15
16    fn compress_pda_account(
17        &self,
18        account_info: &AccountInfo<'info>,
19        meta: &CompressedAccountMetaNoLamportsNoAddress,
20        cpi_accounts: &crate::cpi::v2::CpiAccounts<'_, 'info>,
21        compression_config: &crate::compressible::CompressibleConfig,
22        program_id: &Pubkey,
23    ) -> Result<Option<CompressedAccountInfo>, ProgramError>;
24}
25
26#[inline(never)]
27#[allow(clippy::too_many_arguments)]
28pub fn process_compress_pda_accounts_idempotent<'info, Ctx>(
29    ctx: &Ctx,
30    remaining_accounts: &[AccountInfo<'info>],
31    compressed_accounts: Vec<CompressedAccountMetaNoLamportsNoAddress>,
32    system_accounts_offset: u8,
33    cpi_signer: CpiSigner,
34    program_id: &Pubkey,
35) -> Result<(), ProgramError>
36where
37    Ctx: CompressContext<'info>,
38{
39    use crate::cpi::{
40        v2::{CpiAccounts, LightSystemProgramCpi},
41        InvokeLightSystemProgram, LightCpiInstruction,
42    };
43
44    let proof = crate::instruction::ValidityProof::new(None);
45
46    let compression_config =
47        crate::compressible::CompressibleConfig::load_checked(ctx.config(), program_id)?;
48
49    if *ctx.rent_sponsor().key != compression_config.rent_sponsor {
50        return Err(ProgramError::Custom(0));
51    }
52    if *ctx.compression_authority().key != compression_config.compression_authority {
53        return Err(ProgramError::Custom(0));
54    }
55
56    let system_accounts_offset_usize = system_accounts_offset as usize;
57    if system_accounts_offset_usize > remaining_accounts.len() {
58        return Err(ProgramError::from(
59            crate::error::LightSdkError::ConstraintViolation,
60        ));
61    }
62
63    let cpi_accounts = CpiAccounts::new(
64        ctx.fee_payer(),
65        &remaining_accounts[system_accounts_offset_usize..],
66        cpi_signer,
67    );
68
69    let mut compressed_pda_infos: Vec<CompressedAccountInfo> =
70        Vec::with_capacity(compressed_accounts.len());
71    let mut pda_indices_to_close: Vec<usize> = Vec::with_capacity(compressed_accounts.len());
72
73    let system_accounts_start = cpi_accounts.system_accounts_end_offset();
74    let account_infos = cpi_accounts.to_account_infos();
75    let all_post_system = account_infos
76        .get(system_accounts_start..)
77        .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
78
79    // PDAs are at the end of remaining_accounts, after all the merkle tree/queue accounts
80    let pda_start_in_all_accounts = all_post_system
81        .len()
82        .checked_sub(compressed_accounts.len())
83        .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
84    let solana_accounts = all_post_system
85        .get(pda_start_in_all_accounts..)
86        .ok_or_else(|| ProgramError::from(crate::error::LightSdkError::ConstraintViolation))?;
87
88    for (i, account_info) in solana_accounts.iter().enumerate() {
89        if account_info.data_is_empty() {
90            continue;
91        }
92
93        if account_info.owner != program_id {
94            continue;
95        }
96
97        let meta = compressed_accounts[i];
98
99        if let Some(compressed_info) = ctx.compress_pda_account(
100            account_info,
101            &meta,
102            &cpi_accounts,
103            &compression_config,
104            program_id,
105        )? {
106            compressed_pda_infos.push(compressed_info);
107            pda_indices_to_close.push(i);
108        }
109    }
110
111    if !compressed_pda_infos.is_empty() {
112        LightSystemProgramCpi::new_cpi(cpi_signer, proof)
113            .with_account_infos(&compressed_pda_infos)
114            .invoke(cpi_accounts.clone())?;
115
116        for idx in pda_indices_to_close {
117            let mut info = solana_accounts[idx].clone();
118            crate::compressible::close::close(&mut info, ctx.rent_sponsor().clone())
119                .map_err(ProgramError::from)?;
120        }
121    }
122
123    Ok(())
124}