light_sdk/cpi/
invoke.rs

1use light_compressed_account::{
2    compressed_account::ReadOnlyCompressedAccount,
3    instruction_data::{
4        cpi_context::CompressedCpiContext,
5        data::{NewAddressParamsPacked, ReadOnlyAddress},
6        invoke_cpi::InstructionDataInvokeCpi,
7        with_account_info::CompressedAccountInfo,
8    },
9};
10use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID};
11
12use crate::{
13    cpi::{to_account_metas, CpiAccounts},
14    error::{LightSdkError, Result},
15    instruction::{account_info::CompressedAccountInfoTrait, ValidityProof},
16    invoke_signed, AccountInfo, AccountMeta, AnchorSerialize, Instruction,
17};
18
19#[derive(Debug, Default, PartialEq, Clone)]
20pub struct CpiInputs {
21    pub proof: ValidityProof,
22    pub account_infos: Option<Vec<CompressedAccountInfo>>,
23    pub read_only_accounts: Option<Vec<ReadOnlyCompressedAccount>>,
24    pub new_addresses: Option<Vec<NewAddressParamsPacked>>,
25    pub read_only_address: Option<Vec<ReadOnlyAddress>>,
26    pub compress_or_decompress_lamports: Option<u64>,
27    pub is_compress: bool,
28    pub cpi_context: Option<CompressedCpiContext>,
29}
30
31impl CpiInputs {
32    pub fn new(proof: ValidityProof, account_infos: Vec<CompressedAccountInfo>) -> Self {
33        Self {
34            proof,
35            account_infos: Some(account_infos),
36            ..Default::default()
37        }
38    }
39
40    pub fn new_with_address(
41        proof: ValidityProof,
42        account_infos: Vec<CompressedAccountInfo>,
43        new_addresses: Vec<NewAddressParamsPacked>,
44    ) -> Self {
45        Self {
46            proof,
47            account_infos: Some(account_infos),
48            new_addresses: Some(new_addresses),
49            ..Default::default()
50        }
51    }
52
53    pub fn invoke_light_system_program(self, cpi_accounts: CpiAccounts) -> Result<()> {
54        let bump = cpi_accounts.bump();
55        let account_info_refs = cpi_accounts.to_account_infos();
56        let instruction = create_light_system_progam_instruction_invoke_cpi(self, cpi_accounts)?;
57        let account_infos: Vec<AccountInfo> = account_info_refs.into_iter().cloned().collect();
58        invoke_light_system_program(account_infos.as_slice(), instruction, bump)
59    }
60}
61
62pub fn create_light_system_progam_instruction_invoke_cpi(
63    cpi_inputs: CpiInputs,
64    cpi_accounts: CpiAccounts,
65) -> Result<Instruction> {
66    let owner = *cpi_accounts.invoking_program()?.key;
67    let (input_compressed_accounts_with_merkle_context, output_compressed_accounts) =
68        if let Some(account_infos) = cpi_inputs.account_infos.as_ref() {
69            let mut input_compressed_accounts_with_merkle_context =
70                Vec::with_capacity(account_infos.len());
71            let mut output_compressed_accounts = Vec::with_capacity(account_infos.len());
72            for account_info in account_infos.iter() {
73                if let Some(input_account) =
74                    account_info.input_compressed_account(owner.to_bytes().into())?
75                {
76                    input_compressed_accounts_with_merkle_context.push(input_account);
77                }
78                if let Some(output_account) =
79                    account_info.output_compressed_account(owner.to_bytes().into())?
80                {
81                    output_compressed_accounts.push(output_account);
82                }
83            }
84            (
85                input_compressed_accounts_with_merkle_context,
86                output_compressed_accounts,
87            )
88        } else {
89            (vec![], vec![])
90        };
91    #[cfg(not(feature = "v2"))]
92    if cpi_inputs.read_only_accounts.is_some() {
93        unimplemented!("read_only_accounts are only supported with v2 soon on Devnet.");
94    }
95    #[cfg(not(feature = "v2"))]
96    if cpi_inputs.read_only_address.is_some() {
97        unimplemented!("read_only_addresses are only supported with v2 soon on Devnet.");
98    }
99
100    let inputs = InstructionDataInvokeCpi {
101        proof: cpi_inputs.proof.into(),
102        new_address_params: cpi_inputs.new_addresses.unwrap_or_default(),
103        relay_fee: None,
104        input_compressed_accounts_with_merkle_context,
105        output_compressed_accounts,
106        compress_or_decompress_lamports: cpi_inputs.compress_or_decompress_lamports,
107        is_compress: cpi_inputs.is_compress,
108        cpi_context: cpi_inputs.cpi_context,
109    };
110    let inputs = inputs.try_to_vec().map_err(|_| LightSdkError::Borsh)?;
111
112    let mut data = Vec::with_capacity(8 + 4 + inputs.len());
113    data.extend_from_slice(&light_compressed_account::discriminators::DISCRIMINATOR_INVOKE_CPI);
114    data.extend_from_slice(&(inputs.len() as u32).to_le_bytes());
115    data.extend(inputs);
116
117    let account_metas: Vec<AccountMeta> = to_account_metas(cpi_accounts)?;
118    Ok(Instruction {
119        program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
120        accounts: account_metas,
121        data,
122    })
123}
124
125/// Invokes the light system program to verify and apply a zk-compressed state
126/// transition. Serializes CPI instruction data, configures necessary accounts,
127/// and executes the CPI.
128pub fn verify_borsh<T>(light_system_accounts: CpiAccounts, inputs: &T) -> Result<()>
129where
130    T: AnchorSerialize,
131{
132    let inputs = inputs.try_to_vec().map_err(|_| LightSdkError::Borsh)?;
133
134    let mut data = Vec::with_capacity(8 + 4 + inputs.len());
135    data.extend_from_slice(&light_compressed_account::discriminators::DISCRIMINATOR_INVOKE_CPI);
136    data.extend_from_slice(&(inputs.len() as u32).to_le_bytes());
137    data.extend(inputs);
138    let account_info_refs = light_system_accounts.to_account_infos();
139    let account_infos: Vec<AccountInfo> = account_info_refs.into_iter().cloned().collect();
140
141    let bump = light_system_accounts.bump();
142    let account_metas: Vec<AccountMeta> = to_account_metas(light_system_accounts)?;
143    let instruction = Instruction {
144        program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
145        accounts: account_metas,
146        data,
147    };
148    invoke_light_system_program(account_infos.as_slice(), instruction, bump)
149}
150
151#[inline(always)]
152pub fn invoke_light_system_program(
153    account_infos: &[AccountInfo],
154    instruction: Instruction,
155    bump: u8,
156) -> Result<()> {
157    let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]];
158
159    // TODO: restore but not a priority it is a convenience check
160    // It's index 0 for small instruction accounts.
161    // if *account_infos[1].key != authority {
162    //     #[cfg(feature = "anchor")]
163    //     anchor_lang::prelude::msg!(
164    //         "System program signer authority is invalid. Expected {:?}, found {:?}",
165    //         authority,
166    //         account_infos[1].key
167    //     );
168    //     #[cfg(feature = "anchor")]
169    //     anchor_lang::prelude::msg!(
170    //         "Seeds to derive expected pubkey: [CPI_AUTHORITY_PDA_SEED] {:?}",
171    //         [CPI_AUTHORITY_PDA_SEED]
172    //     );
173    //     return Err(LightSdkError::InvalidCpiSignerAccount);
174    // }
175
176    invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()])?;
177    Ok(())
178}