Skip to main content

light_sdk/cpi/v1/
invoke.rs

1use light_sdk_types::lca::instruction_data::{
2    compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi,
3};
4
5#[cfg(feature = "poseidon")]
6use crate::{account::poseidon::LightAccount as LightAccountPoseidon, DataHasher};
7use crate::{
8    account::LightAccount,
9    cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner},
10    error::LightSdkError,
11    instruction::account_info::CompressedAccountInfoTrait,
12    AnchorDeserialize, AnchorSerialize, LightDiscriminator, ProgramError,
13};
14
15/// Light system program CPI instruction data builder.
16///
17/// Use this builder to construct instructions for compressed account operations:
18/// creating, updating, closing accounts, and compressing/decompressing SOL.
19///
20/// # Builder Methods
21///
22/// ## Common Methods
23///
24/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data)
25/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses
26/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts
27/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts
28///
29/// **Note**: An instruction can either compress **or** decompress lamports, not both.
30///
31/// ## Advanced Methods
32///
33/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](Self::with_light_account):
34///
35/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts
36/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts
37///
38/// # Examples
39///
40/// ## Create a compressed account with an address
41/// ```rust,no_run
42/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner};
43/// # use light_sdk::instruction::ValidityProof;
44/// # use light_sdk_types::lca::instruction_data::data::NewAddressParamsPacked;
45/// # use light_sdk::{LightAccount, LightDiscriminator};
46/// # use borsh::{BorshSerialize, BorshDeserialize};
47/// # use solana_pubkey::Pubkey;
48/// # use solana_program_error::ProgramError;
49/// #
50/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner {
51/// #     program_id: [0; 32],
52/// #     cpi_signer: [0; 32],
53/// #     bump: 255,
54/// # };
55/// #
56/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
57/// # pub struct MyAccount {
58/// #     pub value: u64,
59/// # }
60/// #
61/// # fn example() -> Result<(), ProgramError> {
62/// # let proof = ValidityProof::default();
63/// # let new_address_params = NewAddressParamsPacked::default();
64/// # let program_id = Pubkey::new_unique();
65/// # let account = LightAccount::<MyAccount>::new_init(&program_id, None, 0);
66/// # let key = Pubkey::new_unique();
67/// # let owner = Pubkey::default();
68/// # let mut lamports = 0u64;
69/// # let mut data = [];
70/// # let fee_payer = &solana_account_info::AccountInfo::new(
71/// #     &key,
72/// #     true,
73/// #     true,
74/// #     &mut lamports,
75/// #     &mut data,
76/// #     &owner,
77/// #     false,
78/// # );
79/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
80/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
81///     .with_new_addresses(&[new_address_params])
82///     .with_light_account(account)?
83///     .invoke(cpi_accounts)?;
84/// # Ok(())
85/// # }
86/// ```
87/// ## Update a compressed account
88/// ```rust,no_run
89/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner};
90/// # use light_sdk::instruction::ValidityProof;
91/// # use light_sdk::{LightAccount, LightDiscriminator};
92/// # use light_sdk::instruction::account_meta::CompressedAccountMeta;
93/// # use borsh::{BorshSerialize, BorshDeserialize};
94/// # use solana_pubkey::Pubkey;
95/// # use solana_program_error::ProgramError;
96/// #
97/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner {
98/// #     program_id: [0; 32],
99/// #     cpi_signer: [0; 32],
100/// #     bump: 255,
101/// # };
102/// #
103/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
104/// # pub struct MyAccount {
105/// #     pub value: u64,
106/// # }
107/// #
108/// # fn example() -> Result<(), ProgramError> {
109/// # let proof = ValidityProof::default();
110/// # let program_id = Pubkey::new_unique();
111/// # let account_meta = CompressedAccountMeta::default();
112/// # let account_data = MyAccount::default();
113/// # let account = LightAccount::<MyAccount>::new_mut(&program_id, &account_meta, account_data)?;
114/// # let key = Pubkey::new_unique();
115/// # let owner = Pubkey::default();
116/// # let mut lamports = 0u64;
117/// # let mut data = [];
118/// # let fee_payer = &solana_account_info::AccountInfo::new(
119/// #     &key,
120/// #     true,
121/// #     true,
122/// #     &mut lamports,
123/// #     &mut data,
124/// #     &owner,
125/// #     false,
126/// # );
127/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
128/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
129///     .with_light_account(account)?
130///     .invoke(cpi_accounts)?;
131/// # Ok(())
132/// # }
133/// ```
134#[derive(Clone)]
135pub struct LightSystemProgramCpi {
136    cpi_signer: CpiSigner,
137    instruction_data: InstructionDataInvokeCpi,
138}
139
140impl LightSystemProgramCpi {
141    #[must_use = "with_new_addresses returns a new value"]
142    pub fn with_new_addresses(
143        mut self,
144        new_address_params: &[light_sdk_types::lca::instruction_data::data::NewAddressParamsPacked],
145    ) -> Self {
146        self.instruction_data = self.instruction_data.with_new_addresses(new_address_params);
147        self
148    }
149
150    #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"]
151    pub fn with_input_compressed_accounts_with_merkle_context(
152        mut self,
153        input_compressed_accounts_with_merkle_context: &[light_sdk_types::lca::compressed_account::PackedCompressedAccountWithMerkleContext],
154    ) -> Self {
155        self.instruction_data = self
156            .instruction_data
157            .with_input_compressed_accounts_with_merkle_context(
158                input_compressed_accounts_with_merkle_context,
159            );
160        self
161    }
162
163    #[must_use = "with_output_compressed_accounts returns a new value"]
164    pub fn with_output_compressed_accounts(
165        mut self,
166        output_compressed_accounts: &[light_sdk_types::lca::instruction_data::data::OutputCompressedAccountWithPackedContext],
167    ) -> Self {
168        self.instruction_data = self
169            .instruction_data
170            .with_output_compressed_accounts(output_compressed_accounts);
171        self
172    }
173
174    #[must_use = "compress_lamports returns a new value"]
175    pub fn compress_lamports(mut self, lamports: u64) -> Self {
176        self.instruction_data = self.instruction_data.compress_lamports(lamports);
177        self
178    }
179
180    #[must_use = "decompress_lamports returns a new value"]
181    pub fn decompress_lamports(mut self, lamports: u64) -> Self {
182        self.instruction_data = self.instruction_data.decompress_lamports(lamports);
183        self
184    }
185
186    #[cfg(feature = "cpi-context")]
187    #[must_use = "write_to_cpi_context_set returns a new value"]
188    pub fn write_to_cpi_context_set(mut self) -> Self {
189        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
190        self
191    }
192
193    #[cfg(feature = "cpi-context")]
194    #[must_use = "write_to_cpi_context_first returns a new value"]
195    pub fn write_to_cpi_context_first(mut self) -> Self {
196        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
197        self
198    }
199
200    #[cfg(feature = "cpi-context")]
201    #[must_use = "with_cpi_context returns a new value"]
202    pub fn with_cpi_context(
203        mut self,
204        cpi_context: light_sdk_types::lca::instruction_data::cpi_context::CompressedCpiContext,
205    ) -> Self {
206        self.instruction_data = self.instruction_data.with_cpi_context(cpi_context);
207        self
208    }
209}
210
211impl LightCpiInstruction for LightSystemProgramCpi {
212    fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self {
213        Self {
214            cpi_signer,
215            instruction_data: InstructionDataInvokeCpi::new(proof.into()),
216        }
217    }
218
219    fn with_light_account<A>(mut self, account: LightAccount<A>) -> Result<Self, ProgramError>
220    where
221        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
222    {
223        use light_sdk_types::lca::compressed_account::PackedCompressedAccountWithMerkleContext;
224
225        // Convert LightAccount to account info
226        let account_info = account.to_account_info()?;
227
228        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
229        if let Some(input_account) = account_info
230            .input_compressed_account(self.cpi_signer.program_id.into())
231            .map_err(LightSdkError::from)
232            .map_err(ProgramError::from)?
233        {
234            let packed_input = PackedCompressedAccountWithMerkleContext {
235                compressed_account: input_account.compressed_account,
236                merkle_context: input_account.merkle_context,
237                root_index: input_account.root_index,
238                read_only: false, // Default to false for v1
239            };
240            self.instruction_data
241                .input_compressed_accounts_with_merkle_context
242                .push(packed_input);
243        }
244
245        // Handle output accounts
246        if let Some(output_account) = account_info
247            .output_compressed_account(self.cpi_signer.program_id.into())
248            .map_err(LightSdkError::from)
249            .map_err(ProgramError::from)?
250        {
251            self.instruction_data
252                .output_compressed_accounts
253                .push(output_account);
254        }
255
256        Ok(self)
257    }
258
259    #[cfg(feature = "poseidon")]
260    fn with_light_account_poseidon<A>(
261        mut self,
262        account: LightAccountPoseidon<A>,
263    ) -> Result<Self, ProgramError>
264    where
265        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default,
266    {
267        use light_sdk_types::lca::compressed_account::PackedCompressedAccountWithMerkleContext;
268
269        // Convert LightAccount to account info
270        let account_info = account.to_account_info()?;
271
272        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
273        if let Some(input_account) = account_info
274            .input_compressed_account(self.cpi_signer.program_id.into())
275            .map_err(LightSdkError::from)
276            .map_err(ProgramError::from)?
277        {
278            let packed_input = PackedCompressedAccountWithMerkleContext {
279                compressed_account: input_account.compressed_account,
280                merkle_context: input_account.merkle_context,
281                root_index: input_account.root_index,
282                read_only: false, // Default to false for v1
283            };
284            self.instruction_data
285                .input_compressed_accounts_with_merkle_context
286                .push(packed_input);
287        }
288
289        // Handle output accounts
290        if let Some(output_account) = account_info
291            .output_compressed_account(self.cpi_signer.program_id.into())
292            .map_err(LightSdkError::from)
293            .map_err(ProgramError::from)?
294        {
295            self.instruction_data
296                .output_compressed_accounts
297                .push(output_account);
298        }
299
300        Ok(self)
301    }
302
303    fn get_mode(&self) -> u8 {
304        0 // V1 uses regular mode by default
305    }
306
307    fn get_bump(&self) -> u8 {
308        self.cpi_signer.bump
309    }
310
311    #[cfg(feature = "cpi-context")]
312    fn write_to_cpi_context_first(mut self) -> Self {
313        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
314        self
315    }
316
317    #[cfg(feature = "cpi-context")]
318    fn write_to_cpi_context_set(mut self) -> Self {
319        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
320        self
321    }
322
323    #[cfg(feature = "cpi-context")]
324    fn execute_with_cpi_context(self) -> Self {
325        // V1 doesn't have a direct execute context, just return self
326        // The execute happens through the invoke call
327        self
328    }
329
330    #[cfg(feature = "cpi-context")]
331    fn get_with_cpi_context(&self) -> bool {
332        self.instruction_data.cpi_context.is_some()
333    }
334
335    #[cfg(feature = "cpi-context")]
336    fn get_cpi_context(
337        &self,
338    ) -> &light_sdk_types::lca::instruction_data::cpi_context::CompressedCpiContext {
339        use light_sdk_types::lca::instruction_data::cpi_context::CompressedCpiContext;
340        // Use a static default with all fields set to false/0
341        static DEFAULT: CompressedCpiContext = CompressedCpiContext {
342            set_context: false,
343            first_set_context: false,
344            cpi_context_account_index: 0,
345        };
346        self.instruction_data
347            .cpi_context
348            .as_ref()
349            .unwrap_or(&DEFAULT)
350    }
351
352    #[cfg(feature = "cpi-context")]
353    fn has_read_only_accounts(&self) -> bool {
354        // V1 doesn't support read-only accounts
355        false
356    }
357}
358
359// Manual BorshSerialize implementation that only serializes instruction_data
360impl AnchorSerialize for LightSystemProgramCpi {
361    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
362        self.instruction_data.serialize(writer)
363    }
364}
365
366impl light_sdk_types::lca::InstructionDiscriminator for LightSystemProgramCpi {
367    fn discriminator(&self) -> &'static [u8] {
368        self.instruction_data.discriminator()
369    }
370}
371
372impl LightInstructionData for LightSystemProgramCpi {
373    fn data(&self) -> Result<Vec<u8>, light_sdk_types::lca::CompressedAccountError> {
374        self.instruction_data.data()
375    }
376}