light_sdk/cpi/v1/
invoke.rs

1use light_compressed_account::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_compressed_account::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/// #     0,
79/// # );
80/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
81/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
82///     .with_new_addresses(&[new_address_params])
83///     .with_light_account(account)?
84///     .invoke(cpi_accounts)?;
85/// # Ok(())
86/// # }
87/// ```
88/// ## Update a compressed account
89/// ```rust,no_run
90/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner};
91/// # use light_sdk::instruction::ValidityProof;
92/// # use light_sdk::{LightAccount, LightDiscriminator};
93/// # use light_sdk::instruction::account_meta::CompressedAccountMeta;
94/// # use borsh::{BorshSerialize, BorshDeserialize};
95/// # use solana_pubkey::Pubkey;
96/// # use solana_program_error::ProgramError;
97/// #
98/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner {
99/// #     program_id: [0; 32],
100/// #     cpi_signer: [0; 32],
101/// #     bump: 255,
102/// # };
103/// #
104/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
105/// # pub struct MyAccount {
106/// #     pub value: u64,
107/// # }
108/// #
109/// # fn example() -> Result<(), ProgramError> {
110/// # let proof = ValidityProof::default();
111/// # let program_id = Pubkey::new_unique();
112/// # let account_meta = CompressedAccountMeta::default();
113/// # let account_data = MyAccount::default();
114/// # let account = LightAccount::<MyAccount>::new_mut(&program_id, &account_meta, account_data)?;
115/// # let key = Pubkey::new_unique();
116/// # let owner = Pubkey::default();
117/// # let mut lamports = 0u64;
118/// # let mut data = [];
119/// # let fee_payer = &solana_account_info::AccountInfo::new(
120/// #     &key,
121/// #     true,
122/// #     true,
123/// #     &mut lamports,
124/// #     &mut data,
125/// #     &owner,
126/// #     false,
127/// #     0,
128/// # );
129/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
130/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
131///     .with_light_account(account)?
132///     .invoke(cpi_accounts)?;
133/// # Ok(())
134/// # }
135/// ```
136#[derive(Clone)]
137pub struct LightSystemProgramCpi {
138    cpi_signer: CpiSigner,
139    instruction_data: InstructionDataInvokeCpi,
140}
141
142impl LightSystemProgramCpi {
143    #[must_use = "with_new_addresses returns a new value"]
144    pub fn with_new_addresses(
145        mut self,
146        new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked],
147    ) -> Self {
148        self.instruction_data = self.instruction_data.with_new_addresses(new_address_params);
149        self
150    }
151
152    #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"]
153    pub fn with_input_compressed_accounts_with_merkle_context(
154        mut self,
155        input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext],
156    ) -> Self {
157        self.instruction_data = self
158            .instruction_data
159            .with_input_compressed_accounts_with_merkle_context(
160                input_compressed_accounts_with_merkle_context,
161            );
162        self
163    }
164
165    #[must_use = "with_output_compressed_accounts returns a new value"]
166    pub fn with_output_compressed_accounts(
167        mut self,
168        output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext],
169    ) -> Self {
170        self.instruction_data = self
171            .instruction_data
172            .with_output_compressed_accounts(output_compressed_accounts);
173        self
174    }
175
176    #[must_use = "compress_lamports returns a new value"]
177    pub fn compress_lamports(mut self, lamports: u64) -> Self {
178        self.instruction_data = self.instruction_data.compress_lamports(lamports);
179        self
180    }
181
182    #[must_use = "decompress_lamports returns a new value"]
183    pub fn decompress_lamports(mut self, lamports: u64) -> Self {
184        self.instruction_data = self.instruction_data.decompress_lamports(lamports);
185        self
186    }
187
188    #[cfg(feature = "cpi-context")]
189    #[must_use = "write_to_cpi_context_set returns a new value"]
190    pub fn write_to_cpi_context_set(mut self) -> Self {
191        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
192        self
193    }
194
195    #[cfg(feature = "cpi-context")]
196    #[must_use = "write_to_cpi_context_first returns a new value"]
197    pub fn write_to_cpi_context_first(mut self) -> Self {
198        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
199        self
200    }
201
202    #[cfg(feature = "cpi-context")]
203    #[must_use = "with_cpi_context returns a new value"]
204    pub fn with_cpi_context(
205        mut self,
206        cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext,
207    ) -> Self {
208        self.instruction_data = self.instruction_data.with_cpi_context(cpi_context);
209        self
210    }
211}
212
213impl LightCpiInstruction for LightSystemProgramCpi {
214    fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self {
215        Self {
216            cpi_signer,
217            instruction_data: InstructionDataInvokeCpi::new(proof.into()),
218        }
219    }
220
221    fn with_light_account<A>(mut self, account: LightAccount<A>) -> Result<Self, ProgramError>
222    where
223        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
224    {
225        use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext;
226
227        // Convert LightAccount to account info
228        let account_info = account.to_account_info()?;
229
230        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
231        if let Some(input_account) = account_info
232            .input_compressed_account(self.cpi_signer.program_id.into())
233            .map_err(LightSdkError::from)
234            .map_err(ProgramError::from)?
235        {
236            let packed_input = PackedCompressedAccountWithMerkleContext {
237                compressed_account: input_account.compressed_account,
238                merkle_context: input_account.merkle_context,
239                root_index: input_account.root_index,
240                read_only: false, // Default to false for v1
241            };
242            self.instruction_data
243                .input_compressed_accounts_with_merkle_context
244                .push(packed_input);
245        }
246
247        // Handle output accounts
248        if let Some(output_account) = account_info
249            .output_compressed_account(self.cpi_signer.program_id.into())
250            .map_err(LightSdkError::from)
251            .map_err(ProgramError::from)?
252        {
253            self.instruction_data
254                .output_compressed_accounts
255                .push(output_account);
256        }
257
258        Ok(self)
259    }
260
261    #[cfg(feature = "poseidon")]
262    fn with_light_account_poseidon<A>(
263        mut self,
264        account: LightAccountPoseidon<A>,
265    ) -> Result<Self, ProgramError>
266    where
267        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default,
268    {
269        use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext;
270
271        // Convert LightAccount to account info
272        let account_info = account.to_account_info()?;
273
274        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
275        if let Some(input_account) = account_info
276            .input_compressed_account(self.cpi_signer.program_id.into())
277            .map_err(LightSdkError::from)
278            .map_err(ProgramError::from)?
279        {
280            let packed_input = PackedCompressedAccountWithMerkleContext {
281                compressed_account: input_account.compressed_account,
282                merkle_context: input_account.merkle_context,
283                root_index: input_account.root_index,
284                read_only: false, // Default to false for v1
285            };
286            self.instruction_data
287                .input_compressed_accounts_with_merkle_context
288                .push(packed_input);
289        }
290
291        // Handle output accounts
292        if let Some(output_account) = account_info
293            .output_compressed_account(self.cpi_signer.program_id.into())
294            .map_err(LightSdkError::from)
295            .map_err(ProgramError::from)?
296        {
297            self.instruction_data
298                .output_compressed_accounts
299                .push(output_account);
300        }
301
302        Ok(self)
303    }
304
305    fn get_mode(&self) -> u8 {
306        0 // V1 uses regular mode by default
307    }
308
309    fn get_bump(&self) -> u8 {
310        self.cpi_signer.bump
311    }
312
313    #[cfg(feature = "cpi-context")]
314    fn write_to_cpi_context_first(mut self) -> Self {
315        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
316        self
317    }
318
319    #[cfg(feature = "cpi-context")]
320    fn write_to_cpi_context_set(mut self) -> Self {
321        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
322        self
323    }
324
325    #[cfg(feature = "cpi-context")]
326    fn execute_with_cpi_context(self) -> Self {
327        // V1 doesn't have a direct execute context, just return self
328        // The execute happens through the invoke call
329        self
330    }
331
332    #[cfg(feature = "cpi-context")]
333    fn get_with_cpi_context(&self) -> bool {
334        self.instruction_data.cpi_context.is_some()
335    }
336
337    #[cfg(feature = "cpi-context")]
338    fn get_cpi_context(
339        &self,
340    ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext {
341        use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext;
342        // Use a static default with all fields set to false/0
343        static DEFAULT: CompressedCpiContext = CompressedCpiContext {
344            set_context: false,
345            first_set_context: false,
346            cpi_context_account_index: 0,
347        };
348        self.instruction_data
349            .cpi_context
350            .as_ref()
351            .unwrap_or(&DEFAULT)
352    }
353
354    #[cfg(feature = "cpi-context")]
355    fn has_read_only_accounts(&self) -> bool {
356        // V1 doesn't support read-only accounts
357        false
358    }
359}
360
361// Manual BorshSerialize implementation that only serializes instruction_data
362impl AnchorSerialize for LightSystemProgramCpi {
363    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
364        self.instruction_data.serialize(writer)
365    }
366}
367
368impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi {
369    fn discriminator(&self) -> &'static [u8] {
370        self.instruction_data.discriminator()
371    }
372}
373
374impl LightInstructionData for LightSystemProgramCpi {
375    fn data(&self) -> Result<Vec<u8>, light_compressed_account::CompressedAccountError> {
376        self.instruction_data.data()
377    }
378}