light_system_program/invoke/
processor.rs

1use account_compression::utils::transfer_lamports::transfer_lamports_cpi;
2use anchor_lang::{prelude::*, Bumps};
3use light_heap::{bench_sbf_end, bench_sbf_start};
4use light_verifier::CompressedProof as CompressedVerifierProof;
5
6use crate::{
7    errors::SystemProgramError,
8    invoke::{
9        address::{derive_new_addresses, insert_addresses_into_address_merkle_tree_queue},
10        append_state::insert_output_compressed_accounts_into_state_merkle_tree,
11        emit_event::emit_state_transition_event,
12        nullify_state::insert_nullifiers,
13        sol_compression::compress_or_decompress_lamports,
14        sum_check::sum_check,
15        verify_state_proof::{
16            fetch_input_compressed_account_roots, fetch_roots_address_merkle_tree,
17            hash_input_compressed_accounts, verify_state_proof,
18        },
19    },
20    sdk::accounts::{InvokeAccounts, SignerAccounts},
21    InstructionDataInvoke,
22};
23
24// TODO: remove once upgraded to anchor 0.30.0 (right now it's required for idl generation)
25#[derive(Debug, Clone, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
26pub struct CompressedProof {
27    pub a: [u8; 32],
28    pub b: [u8; 64],
29    pub c: [u8; 32],
30}
31
32impl Default for CompressedProof {
33    fn default() -> Self {
34        Self {
35            a: [0; 32],
36            b: [0; 64],
37            c: [0; 32],
38        }
39    }
40}
41
42/// Steps:
43/// 1. Sum check
44/// 2. Compression lamports
45/// 3. Verify state inclusion & address non-inclusion proof
46/// 4. Insert nullifiers
47/// 5. Insert output compressed accounts into state Merkle tree
48/// 6. Emit state transition event
49pub fn process<
50    'a,
51    'b,
52    'c: 'info,
53    'info,
54    A: InvokeAccounts<'info> + SignerAccounts<'info> + Bumps,
55>(
56    mut inputs: InstructionDataInvoke,
57    invoking_program: Option<Pubkey>,
58    ctx: Context<'a, 'b, 'c, 'info, A>,
59    cpi_context_inputs: usize,
60) -> Result<()> {
61    if inputs.relay_fee.is_some() {
62        unimplemented!("Relay fee is not implemented yet.");
63    }
64    // Sum check ---------------------------------------------------
65    bench_sbf_start!("cpda_sum_check");
66    sum_check(
67        &inputs.input_compressed_accounts_with_merkle_context,
68        &inputs.output_compressed_accounts,
69        &inputs.relay_fee,
70        &inputs.compress_or_decompress_lamports,
71        &inputs.is_compress,
72    )?;
73    bench_sbf_end!("cpda_sum_check");
74    // Compress or decompress lamports ---------------------------------------------------
75    bench_sbf_start!("cpda_process_compression");
76    if inputs.compress_or_decompress_lamports.is_some() {
77        if inputs.is_compress && ctx.accounts.get_decompression_recipient().is_some() {
78            return err!(SystemProgramError::DecompressionRecipientDefined);
79        }
80        compress_or_decompress_lamports(&inputs, &ctx)?;
81    } else if ctx.accounts.get_decompression_recipient().is_some() {
82        return err!(SystemProgramError::DecompressionRecipientDefined);
83    } else if ctx.accounts.get_sol_pool_pda().is_some() {
84        return err!(SystemProgramError::SolPoolPdaDefined);
85    }
86    bench_sbf_end!("cpda_process_compression");
87
88    // Allocate heap memory here so that we can free memory after function invocations.
89    let num_input_compressed_accounts = inputs.input_compressed_accounts_with_merkle_context.len();
90    let num_new_addresses = inputs.new_address_params.len();
91    let num_output_compressed_accounts = inputs.output_compressed_accounts.len();
92    let mut input_compressed_account_hashes = vec![[0u8; 32]; num_input_compressed_accounts];
93
94    let mut compressed_account_addresses: Vec<Option<[u8; 32]>> =
95        vec![None; num_input_compressed_accounts + num_new_addresses];
96    let mut output_leaf_indices = vec![0u32; num_output_compressed_accounts];
97    let mut output_compressed_account_hashes = vec![[0u8; 32]; num_output_compressed_accounts];
98    // hashed_pubkeys_capacity is the maximum of hashed pubkey the tx could have.
99    // 1 owner pubkey inputs + every remaining account pubkey can be a tree + every output can be owned by a different pubkey
100    // + number of times cpi context account was filled.
101    let hashed_pubkeys_capacity =
102        1 + ctx.remaining_accounts.len() + num_output_compressed_accounts + cpi_context_inputs;
103    let mut hashed_pubkeys = Vec::<(Pubkey, [u8; 32])>::with_capacity(hashed_pubkeys_capacity);
104
105    // Verify state and or address proof ---------------------------------------------------
106    if !inputs
107        .input_compressed_accounts_with_merkle_context
108        .is_empty()
109        || !inputs.new_address_params.is_empty()
110    {
111        // Allocate heap memory here because roots are only used for proof verification.
112        let mut new_address_roots = vec![[0u8; 32]; num_new_addresses];
113        let mut input_compressed_account_roots = vec![[0u8; 32]; num_input_compressed_accounts];
114        // hash input compressed accounts ---------------------------------------------------
115        bench_sbf_start!("cpda_hash_input_compressed_accounts");
116        if !inputs
117            .input_compressed_accounts_with_merkle_context
118            .is_empty()
119        {
120            hash_input_compressed_accounts(
121                ctx.remaining_accounts,
122                &inputs.input_compressed_accounts_with_merkle_context,
123                &mut input_compressed_account_hashes,
124                &mut compressed_account_addresses,
125                &mut hashed_pubkeys,
126            )?;
127            // # Safety this is a safeguard for memory safety.
128            // This error should never be triggered.
129            if hashed_pubkeys.capacity() != hashed_pubkeys_capacity {
130                msg!(
131                    "hashed_pubkeys exceeded capacity. Used {}, allocated {}.",
132                    hashed_pubkeys.capacity(),
133                    hashed_pubkeys_capacity
134                );
135                return err!(SystemProgramError::InvalidCapacity);
136            }
137            fetch_input_compressed_account_roots(
138                &inputs.input_compressed_accounts_with_merkle_context,
139                &ctx,
140                &mut input_compressed_account_roots,
141            )?;
142        }
143
144        bench_sbf_end!("cpda_hash_input_compressed_accounts");
145        let mut new_addresses = vec![[0u8; 32]; num_new_addresses];
146        // Insert addresses into address merkle tree queue ---------------------------------------------------
147        if !new_addresses.is_empty() {
148            derive_new_addresses(
149                &inputs.new_address_params,
150                num_input_compressed_accounts,
151                ctx.remaining_accounts,
152                &mut compressed_account_addresses,
153                &mut new_addresses,
154            )?;
155            let network_fee_bundle = insert_addresses_into_address_merkle_tree_queue(
156                &ctx,
157                &new_addresses,
158                &inputs.new_address_params,
159                &invoking_program,
160            )?;
161            if let Some(network_fee_bundle) = network_fee_bundle {
162                let (remaining_account_index, network_fee) = network_fee_bundle;
163                transfer_lamports_cpi(
164                    ctx.accounts.get_fee_payer(),
165                    &ctx.remaining_accounts[remaining_account_index as usize],
166                    network_fee,
167                )?;
168            }
169            fetch_roots_address_merkle_tree(
170                &inputs.new_address_params,
171                &ctx,
172                &mut new_address_roots,
173            )?;
174        }
175        bench_sbf_start!("cpda_verify_state_proof");
176
177        let proof = match &inputs.proof {
178            Some(proof) => proof,
179            None => return err!(SystemProgramError::ProofIsNone),
180        };
181        let compressed_verifier_proof = CompressedVerifierProof {
182            a: proof.a,
183            b: proof.b,
184            c: proof.c,
185        };
186        match verify_state_proof(
187            &input_compressed_account_roots,
188            &input_compressed_account_hashes,
189            &new_address_roots,
190            &new_addresses,
191            &compressed_verifier_proof,
192        ) {
193            Ok(_) => Ok(()),
194            Err(e) => {
195                msg!(
196                    "input_compressed_accounts_with_merkle_context: {:?}",
197                    inputs.input_compressed_accounts_with_merkle_context
198                );
199                Err(e)
200            }
201        }?;
202        bench_sbf_end!("cpda_verify_state_proof");
203        // insert nullifiers (input compressed account hashes)---------------------------------------------------
204        bench_sbf_start!("cpda_nullifiers");
205        if !inputs
206            .input_compressed_accounts_with_merkle_context
207            .is_empty()
208        {
209            let network_fee_bundle = insert_nullifiers(
210                &inputs,
211                &ctx,
212                &input_compressed_account_hashes,
213                &invoking_program,
214            )?;
215            if let Some(network_fee_bundle) = network_fee_bundle {
216                let (remaining_account_index, network_fee) = network_fee_bundle;
217                transfer_lamports_cpi(
218                    ctx.accounts.get_fee_payer(),
219                    &ctx.remaining_accounts[remaining_account_index as usize],
220                    network_fee,
221                )?;
222            }
223        }
224        bench_sbf_end!("cpda_nullifiers");
225    } else if inputs.proof.is_some() {
226        return err!(SystemProgramError::ProofIsSome);
227    } else if inputs
228        .input_compressed_accounts_with_merkle_context
229        .is_empty()
230        && inputs.new_address_params.is_empty()
231        && inputs.output_compressed_accounts.is_empty()
232    {
233        return err!(SystemProgramError::EmptyInputs);
234    }
235    bench_sbf_end!("cpda_nullifiers");
236
237    // Allocate space for sequence numbers with remaining account length as a
238    // proxy. We cannot allocate heap memory in
239    // insert_output_compressed_accounts_into_state_merkle_tree because it is
240    // heap neutral.
241    let mut sequence_numbers = Vec::with_capacity(ctx.remaining_accounts.len());
242    // Insert leaves (output compressed account hashes) ---------------------------------------------------
243    if !inputs.output_compressed_accounts.is_empty() {
244        bench_sbf_start!("cpda_append");
245        insert_output_compressed_accounts_into_state_merkle_tree(
246            &mut inputs.output_compressed_accounts,
247            &ctx,
248            &mut output_leaf_indices,
249            &mut output_compressed_account_hashes,
250            &mut compressed_account_addresses,
251            &invoking_program,
252            &mut hashed_pubkeys,
253            &mut sequence_numbers,
254        )?;
255        // # Safety this is a safeguard for memory safety.
256        // This error should never be triggered.
257        if hashed_pubkeys.capacity() != hashed_pubkeys_capacity {
258            msg!(
259                "hashed_pubkeys exceeded capacity. Used {}, allocated {}.",
260                hashed_pubkeys.capacity(),
261                hashed_pubkeys_capacity
262            );
263            return err!(SystemProgramError::InvalidCapacity);
264        }
265        bench_sbf_end!("cpda_append");
266    }
267    bench_sbf_start!("emit_state_transition_event");
268    // Reduce the capacity of the sequence numbers vector.
269    sequence_numbers.shrink_to_fit();
270    // Emit state transition event ---------------------------------------------------
271    bench_sbf_start!("emit_state_transition_event");
272    emit_state_transition_event(
273        inputs,
274        &ctx,
275        input_compressed_account_hashes,
276        output_compressed_account_hashes,
277        output_leaf_indices,
278        sequence_numbers,
279    )?;
280    bench_sbf_end!("emit_state_transition_event");
281
282    Ok(())
283}