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        invoke_light_system_program(&account_infos, instruction, self.get_bump())
83    }
84
85    #[cfg(feature = "cpi-context")]
86    fn invoke_write_to_cpi_context_first<'info>(
87        self,
88        accounts: impl CpiAccountsTrait<'info>,
89    ) -> Result<(), ProgramError> {
90        let instruction_data = self.write_to_cpi_context_first();
91        inner_invoke_write_to_cpi_context_typed(instruction_data, accounts)
92    }
93
94    #[cfg(feature = "cpi-context")]
95    fn invoke_write_to_cpi_context_set<'info>(
96        self,
97        accounts: impl CpiAccountsTrait<'info>,
98    ) -> Result<(), ProgramError> {
99        let instruction_data = self.write_to_cpi_context_set();
100        inner_invoke_write_to_cpi_context_typed(instruction_data, accounts)
101    }
102
103    #[cfg(feature = "cpi-context")]
104    fn invoke_execute_cpi_context<'info>(
105        self,
106        accounts: impl CpiAccountsTrait<'info>,
107    ) -> Result<(), ProgramError> {
108        let instruction_data = self.execute_with_cpi_context();
109        // Serialize instruction data with discriminator
110        let data = instruction_data
111            .data()
112            .map_err(LightSdkError::from)
113            .map_err(ProgramError::from)?;
114
115        // Get account infos and metas
116        let account_infos = accounts.to_account_infos();
117        let account_metas = accounts.to_account_metas()?;
118
119        let instruction = Instruction {
120            program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
121            accounts: account_metas,
122            data,
123        };
124        invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump())
125    }
126}
127
128// Generic inner helper for write_to_cpi_context operations
129#[cfg(feature = "cpi-context")]
130#[inline(always)]
131fn inner_invoke_write_to_cpi_context_typed<'info, T>(
132    instruction_data: T,
133    accounts: impl CpiAccountsTrait<'info>,
134) -> Result<(), ProgramError>
135where
136    T: LightInstructionData + LightCpiInstruction,
137{
138    // Check if read-only accounts are present
139    if instruction_data.has_read_only_accounts() {
140        solana_msg::msg!(
141            "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead."
142        );
143        return Err(LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext.into());
144    }
145
146    // Serialize instruction data with discriminator
147    let data = instruction_data
148        .data()
149        .map_err(LightSdkError::from)
150        .map_err(ProgramError::from)?;
151
152    // Get account infos and metas
153    let account_infos = accounts.to_account_infos();
154
155    // Extract account pubkeys from account_infos
156    // Assuming order: [fee_payer, authority, cpi_context, ...]
157    if account_infos.len() < 3 {
158        return Err(ProgramError::NotEnoughAccountKeys);
159    }
160
161    let instruction = Instruction {
162        program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
163        accounts: vec![
164            AccountMeta {
165                pubkey: *account_infos[0].key, // fee_payer
166                is_writable: true,
167                is_signer: true,
168            },
169            AccountMeta {
170                pubkey: *account_infos[1].key, // authority
171                is_writable: false,
172                is_signer: true,
173            },
174            AccountMeta {
175                pubkey: *account_infos[2].key, // cpi_context
176                is_writable: true,
177                is_signer: false,
178            },
179        ],
180        data,
181    };
182
183    invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump())
184}
185
186/// Low-level function to invoke the Light system program with a PDA signer.
187///
188/// **Note**: This is a low-level function. In most cases, you should use the
189/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level
190/// interface with better type safety and ergonomics.
191#[inline(always)]
192pub fn invoke_light_system_program(
193    account_infos: &[AccountInfo],
194    instruction: Instruction,
195    bump: u8,
196) -> Result<(), ProgramError> {
197    let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]];
198    invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()])
199}