Skip to main content

light_sdk/cpi/
invoke.rs

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