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