light_sdk/
verify.rs

1use anchor_lang::{prelude::*, Bumps};
2use solana_program::{instruction::Instruction, program::invoke_signed};
3
4use crate::{
5    address::NewAddressParamsPacked,
6    compressed_account::{
7        OutputCompressedAccountWithPackedContext, PackedCompressedAccountWithMerkleContext,
8    },
9    error::LightSdkError,
10    proof::CompressedProof,
11    traits::{
12        InvokeAccounts, InvokeCpiAccounts, InvokeCpiContextAccount, LightSystemAccount,
13        SignerAccounts,
14    },
15    CPI_AUTHORITY_PDA_SEED, PROGRAM_ID_LIGHT_SYSTEM,
16};
17
18pub fn find_cpi_signer(program_id: &Pubkey) -> Pubkey {
19    Pubkey::find_program_address([CPI_AUTHORITY_PDA_SEED].as_slice(), program_id).0
20}
21
22#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub struct CompressedCpiContext {
24    /// Is set by the program that is invoking the CPI to signal that is should
25    /// set the cpi context.
26    pub set_context: bool,
27    /// Is set to wipe the cpi context since someone could have set it before
28    /// with unrelated data.
29    pub first_set_context: bool,
30    /// Index of cpi context account in remaining accounts.
31    pub cpi_context_account_index: u8,
32}
33
34#[derive(Debug, PartialEq, Default, Clone, AnchorDeserialize, AnchorSerialize)]
35pub struct InstructionDataInvokeCpi {
36    pub proof: Option<CompressedProof>,
37    pub new_address_params: Vec<NewAddressParamsPacked>,
38    pub input_compressed_accounts_with_merkle_context:
39        Vec<PackedCompressedAccountWithMerkleContext>,
40    pub output_compressed_accounts: Vec<OutputCompressedAccountWithPackedContext>,
41    pub relay_fee: Option<u64>,
42    pub compress_or_decompress_lamports: Option<u64>,
43    pub is_compress: bool,
44    pub cpi_context: Option<CompressedCpiContext>,
45}
46
47#[inline(always)]
48pub fn setup_cpi_accounts<'info>(
49    ctx: &Context<
50        '_,
51        '_,
52        '_,
53        'info,
54        impl InvokeAccounts<'info>
55            + LightSystemAccount<'info>
56            + InvokeCpiAccounts<'info>
57            + SignerAccounts<'info>
58            + InvokeCpiContextAccount<'info>
59            + Bumps,
60    >,
61) -> (Vec<AccountInfo<'info>>, Vec<AccountMeta>) {
62    // The trick for having `None` accounts is to pass program ID, see
63    // https://github.com/coral-xyz/anchor/pull/2101
64    let none_account_info = ctx.accounts.get_light_system_program().to_account_info();
65
66    let (cpi_context_account_info, cpi_context_account_meta) =
67        match ctx.accounts.get_cpi_context_account() {
68            Some(acc) => (
69                acc.to_account_info(),
70                AccountMeta {
71                    pubkey: acc.key(),
72                    is_signer: false,
73                    is_writable: true,
74                },
75            ),
76            None => (
77                none_account_info.clone(),
78                AccountMeta {
79                    pubkey: ctx.accounts.get_light_system_program().key(),
80                    is_signer: false,
81                    is_writable: false,
82                },
83            ),
84        };
85
86    let mut account_infos = vec![
87        // fee_payer
88        ctx.accounts.get_fee_payer().to_account_info(),
89        // authority
90        ctx.accounts.get_authority().to_account_info(),
91        // registered_program_pda
92        ctx.accounts.get_registered_program_pda().to_account_info(),
93        // noop_program
94        ctx.accounts.get_noop_program().to_account_info(),
95        // account_compression_authority
96        ctx.accounts
97            .get_account_compression_authority()
98            .to_account_info(),
99        // account_compression_program
100        ctx.accounts
101            .get_account_compression_program()
102            .to_account_info(),
103        // invoking_program
104        ctx.accounts.get_invoking_program().to_account_info(),
105        // sol_pool_pda
106        none_account_info.clone(),
107        // decompression_recipient
108        none_account_info,
109        // system_program
110        ctx.accounts.get_system_program().to_account_info(),
111        // cpi_context_account
112        cpi_context_account_info,
113    ];
114    for remaining_account in ctx.remaining_accounts {
115        account_infos.push(remaining_account.to_owned());
116    }
117
118    let mut account_metas = vec![
119        // fee_payer
120        AccountMeta {
121            pubkey: account_infos[0].key(),
122            is_signer: true,
123            is_writable: true,
124        },
125        // authority
126        AccountMeta {
127            pubkey: account_infos[1].key(),
128            is_signer: true,
129            is_writable: false,
130        },
131        // registered_program_pda
132        AccountMeta {
133            pubkey: account_infos[2].key(),
134            is_signer: false,
135            is_writable: false,
136        },
137        // noop_program
138        AccountMeta {
139            pubkey: account_infos[3].key(),
140            is_signer: false,
141            is_writable: false,
142        },
143        // account_compression_authority
144        AccountMeta {
145            pubkey: account_infos[4].key(),
146            is_signer: false,
147            is_writable: false,
148        },
149        // account_compression_program
150        AccountMeta {
151            pubkey: account_infos[5].key(),
152            is_signer: false,
153            is_writable: false,
154        },
155        // invoking_program
156        AccountMeta {
157            pubkey: account_infos[6].key(),
158            is_signer: false,
159            is_writable: false,
160        },
161        // sol_pool_pda
162        AccountMeta {
163            pubkey: account_infos[7].key(),
164            is_signer: false,
165            is_writable: false,
166        },
167        // decompression_recipient
168        AccountMeta {
169            pubkey: account_infos[8].key(),
170            is_signer: false,
171            is_writable: false,
172        },
173        // system_program
174        AccountMeta {
175            pubkey: account_infos[9].key(),
176            is_signer: false,
177            is_writable: false,
178        },
179        cpi_context_account_meta,
180    ];
181    for remaining_account in ctx.remaining_accounts {
182        account_metas.extend(remaining_account.to_account_metas(None));
183    }
184
185    (account_infos, account_metas)
186}
187
188#[derive(AnchorDeserialize, AnchorSerialize)]
189pub struct InvokeCpi {
190    pub inputs: Vec<u8>,
191}
192
193#[inline(always)]
194pub fn invoke_cpi(
195    account_infos: &[AccountInfo],
196    accounts_metas: Vec<AccountMeta>,
197    inputs: Vec<u8>,
198    signer_seeds: &[&[&[u8]]],
199) -> Result<()> {
200    let instruction_data = InvokeCpi { inputs };
201
202    // `InvokeCpi`'s discriminator
203    let mut data = [49, 212, 191, 129, 39, 194, 43, 196].to_vec();
204    data.extend(instruction_data.try_to_vec()?);
205
206    let instruction = Instruction {
207        program_id: PROGRAM_ID_LIGHT_SYSTEM,
208        accounts: accounts_metas,
209        data,
210    };
211    invoke_signed(&instruction, account_infos, signer_seeds)?;
212
213    Ok(())
214}
215
216/// Invokes the light system program to verify and apply a zk-compressed state
217/// transition. Serializes CPI instruction data, configures necessary accounts,
218/// and executes the CPI.
219pub fn verify<'info, 'a, 'b, 'c, T>(
220    ctx: &Context<
221        '_,
222        '_,
223        '_,
224        'info,
225        impl InvokeAccounts<'info>
226            + LightSystemAccount<'info>
227            + InvokeCpiAccounts<'info>
228            + SignerAccounts<'info>
229            + InvokeCpiContextAccount<'info>
230            + Bumps,
231    >,
232    inputs: &T,
233    signer_seeds: &'a [&'b [&'c [u8]]],
234) -> Result<()>
235where
236    T: AnchorSerialize,
237{
238    if ctx.accounts.get_light_system_program().key() != PROGRAM_ID_LIGHT_SYSTEM {
239        return err!(LightSdkError::InvalidLightSystemProgram);
240    }
241
242    let inputs = inputs.try_to_vec()?;
243
244    let (account_infos, account_metas) = setup_cpi_accounts(ctx);
245    invoke_cpi(&account_infos, account_metas, inputs, signer_seeds)?;
246    Ok(())
247}