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    // Check if read-only accounts are present
140    if instruction_data.has_read_only_accounts() {
141        solana_msg::msg!(
142            "Read-only accounts are not supported in write_to_cpi_context operations. Use invoke_execute_cpi_context() instead."
143        );
144        return Err(LightSdkError::ReadOnlyAccountsNotSupportedInCpiContext.into());
145    }
146
147    // Serialize instruction data with discriminator
148    let data = instruction_data
149        .data()
150        .map_err(LightSdkError::from)
151        .map_err(ProgramError::from)?;
152
153    // Get account infos and metas
154    let account_infos = accounts.to_account_infos();
155
156    // Extract account pubkeys from account_infos
157    // Assuming order: [fee_payer, authority, cpi_context, ...]
158    if account_infos.len() < 3 {
159        return Err(ProgramError::NotEnoughAccountKeys);
160    }
161
162    let instruction = Instruction {
163        program_id: LIGHT_SYSTEM_PROGRAM_ID.into(),
164        accounts: vec![
165            AccountMeta {
166                pubkey: *account_infos[0].key, // fee_payer
167                is_writable: true,
168                is_signer: true,
169            },
170            AccountMeta {
171                pubkey: *account_infos[1].key, // authority
172                is_writable: false,
173                is_signer: true,
174            },
175            AccountMeta {
176                pubkey: *account_infos[2].key, // cpi_context
177                is_writable: true,
178                is_signer: false,
179            },
180        ],
181        data,
182    };
183
184    invoke_light_system_program(&account_infos, instruction, instruction_data.get_bump())
185}
186
187/// Low-level function to invoke the Light system program with a PDA signer.
188///
189/// **Note**: This is a low-level function. In most cases, you should use the
190/// [`InvokeLightSystemProgram`] trait methods instead, which provide a higher-level
191/// interface with better type safety and ergonomics.
192#[inline(always)]
193pub fn invoke_light_system_program(
194    account_infos: &[AccountInfo],
195    instruction: Instruction,
196    bump: u8,
197) -> Result<(), ProgramError> {
198    let signer_seeds = [CPI_AUTHORITY_PDA_SEED, &[bump]];
199    invoke_signed(&instruction, account_infos, &[signer_seeds.as_slice()])
200}