light_sdk/cpi/
invoke.rs

1pub use light_compressed_account::LightInstructionData;
2use light_sdk_types::constants::{CPI_AUTHORITY_PDA_SEED, LIGHT_SYSTEM_PROGRAM_ID};
3
4#[cfg(feature = "cpi-context")]
5use crate::AccountMeta;
6use crate::{
7    cpi::{account::CpiAccountsTrait, instruction::LightCpiInstruction},
8    error::LightSdkError,
9    invoke_signed, AccountInfo, Instruction, ProgramError,
10};
11
12pub trait InvokeLightSystemProgram {
13    fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError>;
14
15    #[cfg(feature = "cpi-context")]
16    fn invoke_write_to_cpi_context_first<'info>(
17        self,
18        accounts: impl CpiAccountsTrait<'info>,
19    ) -> Result<(), ProgramError>;
20
21    #[cfg(feature = "cpi-context")]
22    fn invoke_write_to_cpi_context_set<'info>(
23        self,
24        accounts: impl CpiAccountsTrait<'info>,
25    ) -> Result<(), ProgramError>;
26
27    #[cfg(feature = "cpi-context")]
28    fn invoke_execute_cpi_context<'info>(
29        self,
30        accounts: impl CpiAccountsTrait<'info>,
31    ) -> Result<(), ProgramError>;
32}
33
34// Blanket implementation for types that implement both LightInstructionData and LightCpiInstruction
35impl<T> InvokeLightSystemProgram for T
36where
37    T: LightInstructionData + LightCpiInstruction,
38{
39    fn invoke<'info>(self, accounts: impl CpiAccountsTrait<'info>) -> Result<(), ProgramError> {
40        #[cfg(feature = "cpi-context")]
41        {
42            // Check if CPI context operations are being attempted
43            use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext;
44            if self.get_with_cpi_context()
45                || *self.get_cpi_context() == CompressedCpiContext::set()
46                || *self.get_cpi_context() == CompressedCpiContext::first()
47            {
48                solana_msg::msg!(
49                    "CPI context operations not supported in invoke(). Use invoke_write_to_cpi_context_first(), invoke_write_to_cpi_context_set(), or invoke_execute_cpi_context() instead"
50                );
51                return Err(ProgramError::InvalidInstructionData);
52            }
53        }
54
55        // Validate mode consistency
56        if let Some(account_mode) = accounts.get_mode() {
57            if account_mode != self.get_mode() {
58                solana_msg::msg!(
59                    "Mode mismatch: accounts have mode {} but instruction data has mode {}",
60                    account_mode,
61                    self.get_mode()
62                );
63                return Err(ProgramError::InvalidInstructionData);
64            }
65        }
66
67        // Serialize instruction data with discriminator
68        let data = self
69            .data()
70            .map_err(LightSdkError::from)
71            .map_err(ProgramError::from)?;
72
73        // Get account infos and metas
74        let account_infos = accounts.to_account_infos();
75        let account_metas = accounts.to_account_metas()?;
76
77        let instruction = Instruction {
78            program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
79            accounts: account_metas,
80            data,
81        };
82
83        invoke_light_system_program(&account_infos, instruction, self.get_bump())
84    }
85
86    #[cfg(feature = "cpi-context")]
87    fn invoke_write_to_cpi_context_first<'info>(
88        self,
89        accounts: impl CpiAccountsTrait<'info>,
90    ) -> Result<(), ProgramError> {
91        let instruction_data = self.write_to_cpi_context_first();
92        inner_invoke_write_to_cpi_context_typed(instruction_data, accounts)
93    }
94
95    #[cfg(feature = "cpi-context")]
96    fn invoke_write_to_cpi_context_set<'info>(
97        self,
98        accounts: impl CpiAccountsTrait<'info>,
99    ) -> Result<(), ProgramError> {
100        let instruction_data = self.write_to_cpi_context_set();
101        inner_invoke_write_to_cpi_context_typed(instruction_data, accounts)
102    }
103
104    #[cfg(feature = "cpi-context")]
105    fn invoke_execute_cpi_context<'info>(
106        self,
107        accounts: impl CpiAccountsTrait<'info>,
108    ) -> Result<(), ProgramError> {
109        let instruction_data = self.execute_with_cpi_context();
110        // Serialize instruction data with discriminator
111        let data = instruction_data
112            .data()
113            .map_err(LightSdkError::from)
114            .map_err(ProgramError::from)?;
115
116        // Get account infos and metas
117        let account_infos = accounts.to_account_infos();
118        let account_metas = accounts.to_account_metas()?;
119
120        let instruction = Instruction {
121            program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
122            accounts: account_metas,
123            data,
124        };
125        invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump())
126    }
127}
128
129// Generic inner helper for write_to_cpi_context operations
130#[cfg(feature = "cpi-context")]
131#[inline(always)]
132fn inner_invoke_write_to_cpi_context_typed<'info, T>(
133    instruction_data: T,
134    accounts: impl CpiAccountsTrait<'info>,
135) -> Result<(), ProgramError>
136where
137    T: LightInstructionData + LightCpiInstruction,
138{
139    // Serialize instruction data with discriminator
140    let data = instruction_data
141        .data()
142        .map_err(LightSdkError::from)
143        .map_err(ProgramError::from)?;
144
145    // Get account infos and metas
146    let account_infos = accounts.to_account_infos();
147
148    // Extract account pubkeys from account_infos
149    // Assuming order: [fee_payer, authority, cpi_context, ...]
150    if account_infos.len() < 3 {
151        return Err(ProgramError::NotEnoughAccountKeys);
152    }
153
154    let instruction = Instruction {
155        program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
156        accounts: vec![
157            AccountMeta {
158                pubkey: *account_infos[0].key, // fee_payer
159                is_writable: true,
160                is_signer: true,
161            },
162            AccountMeta {
163                pubkey: *account_infos[1].key, // authority
164                is_writable: false,
165                is_signer: true,
166            },
167            AccountMeta {
168                pubkey: *account_infos[2].key, // cpi_context
169                is_writable: true,
170                is_signer: false,
171            },
172        ],
173        data,
174    };
175
176    invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump())
177}
178
179/// Low-level function to invoke the Light system program with a PDA signer.
180///
181/// **Note**: This is a low-level function. In most cases, you should use the
182/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level
183/// interface with better type safety and ergonomics.
184#[inline(always)]
185pub fn invoke_light_system_program(
186    account_infos: &[AccountInfo],
187    instruction: Instruction,
188    bump: u8,
189) -> Result<(), ProgramError> {
190    let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]];
191    invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()])
192}