use account_compression::utils::transfer_lamports::transfer_lamports_cpi;
use anchor_lang::{prelude::*, Bumps};
use light_heap::{bench_sbf_end, bench_sbf_start};
use light_verifier::CompressedProof as CompressedVerifierProof;
use crate::{
errors::SystemProgramError,
invoke::{
address::{derive_new_addresses, insert_addresses_into_address_merkle_tree_queue},
append_state::insert_output_compressed_accounts_into_state_merkle_tree,
emit_event::emit_state_transition_event,
nullify_state::insert_nullifiers,
sol_compression::compress_or_decompress_lamports,
sum_check::sum_check,
verify_state_proof::{
fetch_input_compressed_account_roots, fetch_roots_address_merkle_tree,
hash_input_compressed_accounts, verify_state_proof,
},
},
sdk::accounts::{InvokeAccounts, SignerAccounts},
InstructionDataInvoke,
};
#[derive(Debug, Clone, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
pub struct CompressedProof {
pub a: [u8; 32],
pub b: [u8; 64],
pub c: [u8; 32],
}
impl Default for CompressedProof {
fn default() -> Self {
Self {
a: [0; 32],
b: [0; 64],
c: [0; 32],
}
}
}
pub fn process<
'a,
'b,
'c: 'info,
'info,
A: InvokeAccounts<'info> + SignerAccounts<'info> + Bumps,
>(
mut inputs: InstructionDataInvoke,
invoking_program: Option<Pubkey>,
ctx: Context<'a, 'b, 'c, 'info, A>,
cpi_context_inputs: usize,
) -> Result<()> {
if inputs.relay_fee.is_some() {
unimplemented!("Relay fee is not implemented yet.");
}
bench_sbf_start!("cpda_sum_check");
sum_check(
&inputs.input_compressed_accounts_with_merkle_context,
&inputs.output_compressed_accounts,
&inputs.relay_fee,
&inputs.compress_or_decompress_lamports,
&inputs.is_compress,
)?;
bench_sbf_end!("cpda_sum_check");
bench_sbf_start!("cpda_process_compression");
if inputs.compress_or_decompress_lamports.is_some() {
if inputs.is_compress && ctx.accounts.get_decompression_recipient().is_some() {
return err!(SystemProgramError::DecompressionRecipientDefined);
}
compress_or_decompress_lamports(&inputs, &ctx)?;
} else if ctx.accounts.get_decompression_recipient().is_some() {
return err!(SystemProgramError::DecompressionRecipientDefined);
} else if ctx.accounts.get_sol_pool_pda().is_some() {
return err!(SystemProgramError::SolPoolPdaDefined);
}
bench_sbf_end!("cpda_process_compression");
let num_input_compressed_accounts = inputs.input_compressed_accounts_with_merkle_context.len();
let num_new_addresses = inputs.new_address_params.len();
let num_output_compressed_accounts = inputs.output_compressed_accounts.len();
let mut input_compressed_account_hashes = vec![[0u8; 32]; num_input_compressed_accounts];
let mut compressed_account_addresses: Vec<Option<[u8; 32]>> =
vec![None; num_input_compressed_accounts + num_new_addresses];
let mut output_leaf_indices = vec![0u32; num_output_compressed_accounts];
let mut output_compressed_account_hashes = vec![[0u8; 32]; num_output_compressed_accounts];
let hashed_pubkeys_capacity =
1 + ctx.remaining_accounts.len() + num_output_compressed_accounts + cpi_context_inputs;
let mut hashed_pubkeys = Vec::<(Pubkey, [u8; 32])>::with_capacity(hashed_pubkeys_capacity);
if !inputs
.input_compressed_accounts_with_merkle_context
.is_empty()
|| !inputs.new_address_params.is_empty()
{
let mut new_address_roots = vec![[0u8; 32]; num_new_addresses];
let mut input_compressed_account_roots = vec![[0u8; 32]; num_input_compressed_accounts];
bench_sbf_start!("cpda_hash_input_compressed_accounts");
if !inputs
.input_compressed_accounts_with_merkle_context
.is_empty()
{
hash_input_compressed_accounts(
ctx.remaining_accounts,
&inputs.input_compressed_accounts_with_merkle_context,
&mut input_compressed_account_hashes,
&mut compressed_account_addresses,
&mut hashed_pubkeys,
)?;
if hashed_pubkeys.capacity() != hashed_pubkeys_capacity {
msg!(
"hashed_pubkeys exceeded capacity. Used {}, allocated {}.",
hashed_pubkeys.capacity(),
hashed_pubkeys_capacity
);
return err!(SystemProgramError::InvalidCapacity);
}
fetch_input_compressed_account_roots(
&inputs.input_compressed_accounts_with_merkle_context,
&ctx,
&mut input_compressed_account_roots,
)?;
}
bench_sbf_end!("cpda_hash_input_compressed_accounts");
let mut new_addresses = vec![[0u8; 32]; num_new_addresses];
if !new_addresses.is_empty() {
derive_new_addresses(
&inputs.new_address_params,
num_input_compressed_accounts,
ctx.remaining_accounts,
&mut compressed_account_addresses,
&mut new_addresses,
)?;
let network_fee_bundle = insert_addresses_into_address_merkle_tree_queue(
&ctx,
&new_addresses,
&inputs.new_address_params,
&invoking_program,
)?;
if let Some(network_fee_bundle) = network_fee_bundle {
let (remaining_account_index, network_fee) = network_fee_bundle;
transfer_lamports_cpi(
ctx.accounts.get_fee_payer(),
&ctx.remaining_accounts[remaining_account_index as usize],
network_fee,
)?;
}
fetch_roots_address_merkle_tree(
&inputs.new_address_params,
&ctx,
&mut new_address_roots,
)?;
}
bench_sbf_start!("cpda_verify_state_proof");
let proof = match &inputs.proof {
Some(proof) => proof,
None => return err!(SystemProgramError::ProofIsNone),
};
let compressed_verifier_proof = CompressedVerifierProof {
a: proof.a,
b: proof.b,
c: proof.c,
};
match verify_state_proof(
&input_compressed_account_roots,
&input_compressed_account_hashes,
&new_address_roots,
&new_addresses,
&compressed_verifier_proof,
) {
Ok(_) => Ok(()),
Err(e) => {
msg!(
"input_compressed_accounts_with_merkle_context: {:?}",
inputs.input_compressed_accounts_with_merkle_context
);
Err(e)
}
}?;
bench_sbf_end!("cpda_verify_state_proof");
bench_sbf_start!("cpda_nullifiers");
if !inputs
.input_compressed_accounts_with_merkle_context
.is_empty()
{
let network_fee_bundle = insert_nullifiers(
&inputs,
&ctx,
&input_compressed_account_hashes,
&invoking_program,
)?;
if let Some(network_fee_bundle) = network_fee_bundle {
let (remaining_account_index, network_fee) = network_fee_bundle;
transfer_lamports_cpi(
ctx.accounts.get_fee_payer(),
&ctx.remaining_accounts[remaining_account_index as usize],
network_fee,
)?;
}
}
bench_sbf_end!("cpda_nullifiers");
} else if inputs.proof.is_some() {
return err!(SystemProgramError::ProofIsSome);
} else if inputs
.input_compressed_accounts_with_merkle_context
.is_empty()
&& inputs.new_address_params.is_empty()
&& inputs.output_compressed_accounts.is_empty()
{
return err!(SystemProgramError::EmptyInputs);
}
bench_sbf_end!("cpda_nullifiers");
let mut sequence_numbers = Vec::with_capacity(ctx.remaining_accounts.len());
if !inputs.output_compressed_accounts.is_empty() {
bench_sbf_start!("cpda_append");
insert_output_compressed_accounts_into_state_merkle_tree(
&mut inputs.output_compressed_accounts,
&ctx,
&mut output_leaf_indices,
&mut output_compressed_account_hashes,
&mut compressed_account_addresses,
&invoking_program,
&mut hashed_pubkeys,
&mut sequence_numbers,
)?;
if hashed_pubkeys.capacity() != hashed_pubkeys_capacity {
msg!(
"hashed_pubkeys exceeded capacity. Used {}, allocated {}.",
hashed_pubkeys.capacity(),
hashed_pubkeys_capacity
);
return err!(SystemProgramError::InvalidCapacity);
}
bench_sbf_end!("cpda_append");
}
bench_sbf_start!("emit_state_transition_event");
sequence_numbers.shrink_to_fit();
bench_sbf_start!("emit_state_transition_event");
emit_state_transition_event(
inputs,
&ctx,
input_compressed_account_hashes,
output_compressed_account_hashes,
output_leaf_indices,
sequence_numbers,
)?;
bench_sbf_end!("emit_state_transition_event");
Ok(())
}