light-program-test 0.23.0

A fast local test environment for Solana programs using compressed accounts and tokens.
Documentation
use account_compression::{
    instruction::InitializeStateMerkleTreeAndNullifierQueue, NullifierQueueConfig,
    StateMerkleTreeConfig,
};
use anchor_lang::{InstructionData, ToAccountMetas};
use light_client::rpc::{errors::RpcError, Rpc};
use light_compressed_account::instruction_data::insert_into_queues::InsertIntoQueuesInstructionDataMut;
use light_registry::protocol_config::state::ProtocolConfig;
use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    signature::{Keypair, Signature, Signer},
    transaction::Transaction,
};

use crate::utils::create_account::create_account_instruction;

#[allow(clippy::too_many_arguments)]
pub fn create_initialize_merkle_tree_instruction(
    payer: Pubkey,
    registered_program_pda: Option<Pubkey>,
    merkle_tree_pubkey: Pubkey,
    nullifier_queue_pubkey: Pubkey,
    state_merkle_tree_config: StateMerkleTreeConfig,
    nullifier_queue_config: NullifierQueueConfig,
    program_owner: Option<Pubkey>,
    forester: Option<Pubkey>,
    index: u64,
) -> Instruction {
    let instruction_data = InitializeStateMerkleTreeAndNullifierQueue {
        index,
        program_owner,
        forester,
        state_merkle_tree_config,
        nullifier_queue_config,
        additional_bytes: 0,
    };
    let registered_program = match registered_program_pda {
        Some(registered_program_pda) => AccountMeta::new(registered_program_pda, false),
        None => AccountMeta::new(account_compression::ID, false),
    };
    Instruction {
        program_id: account_compression::ID,
        accounts: vec![
            AccountMeta::new(payer, true),
            AccountMeta::new(merkle_tree_pubkey, false),
            AccountMeta::new(nullifier_queue_pubkey, false),
            registered_program,
        ],
        data: instruction_data.data(),
    }
}

pub fn create_insert_leaves_instruction(
    leaves: Vec<(u8, [u8; 32])>,
    authority: Pubkey,
    merkle_tree_pubkeys: Vec<Pubkey>,
) -> Instruction {
    let mut bytes = vec![
        0u8;
        InsertIntoQueuesInstructionDataMut::required_size_for_capacity(
            leaves.len() as u8,
            0,
            0,
            merkle_tree_pubkeys.len() as u8,
            0,
            0,
        )
    ];
    let (mut ix_data, _) = InsertIntoQueuesInstructionDataMut::new_at(
        &mut bytes,
        leaves.len() as u8,
        0,
        0,
        merkle_tree_pubkeys.len() as u8,
        0,
        0,
    )
    .unwrap();
    ix_data.num_output_queues = merkle_tree_pubkeys.len() as u8;
    for (i, (index, leaf)) in leaves.iter().enumerate() {
        ix_data.leaves[i].leaf = *leaf;
        ix_data.leaves[i].account_index = *index;
    }

    let instruction_data = account_compression::instruction::InsertIntoQueues { bytes };

    let accounts = account_compression::accounts::GenericInstruction { authority };
    let merkle_tree_account_metas = merkle_tree_pubkeys
        .iter()
        .map(|pubkey| AccountMeta::new(*pubkey, false))
        .collect::<Vec<AccountMeta>>();

    Instruction {
        program_id: account_compression::ID,
        accounts: [
            accounts.to_account_metas(Some(true)),
            merkle_tree_account_metas,
        ]
        .concat(),
        data: instruction_data.data(),
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn create_state_merkle_tree_and_queue_account<R: Rpc>(
    payer: &Keypair,
    registry: bool,
    rpc: &mut R,
    merkle_tree_keypair: &Keypair,
    nullifier_queue_keypair: &Keypair,
    cpi_context_keypair: Option<&Keypair>,
    program_owner: Option<Pubkey>,
    forester: Option<Pubkey>,
    index: u64,
    merkle_tree_config: &StateMerkleTreeConfig,
    queue_config: &NullifierQueueConfig,
) -> Result<Signature, RpcError> {
    use light_registry::account_compression_cpi::sdk::create_initialize_merkle_tree_instruction as create_initialize_merkle_tree_instruction_registry;
    let size = account_compression::state::StateMerkleTreeAccount::size(
        merkle_tree_config.height as usize,
        merkle_tree_config.changelog_size as usize,
        merkle_tree_config.roots_size as usize,
        merkle_tree_config.canopy_depth as usize,
    );

    let merkle_tree_account_create_ix = create_account_instruction(
        &payer.pubkey(),
        size,
        rpc.get_minimum_balance_for_rent_exemption(size)
            .await
            .unwrap(),
        &account_compression::ID,
        Some(merkle_tree_keypair),
    );
    let size =
        account_compression::state::queue::QueueAccount::size(queue_config.capacity as usize)
            .unwrap();
    let nullifier_queue_account_create_ix = create_account_instruction(
        &payer.pubkey(),
        size,
        rpc.get_minimum_balance_for_rent_exemption(size)
            .await
            .unwrap(),
        &account_compression::ID,
        Some(nullifier_queue_keypair),
    );

    let transaction = if registry {
        let cpi_context_keypair = cpi_context_keypair.unwrap();
        let rent_cpi_config = rpc
            .get_minimum_balance_for_rent_exemption(
                ProtocolConfig::default().cpi_context_size as usize,
            )
            .await
            .unwrap();
        let create_cpi_context_instruction = create_account_instruction(
            &payer.pubkey(),
            ProtocolConfig::default().cpi_context_size as usize,
            rent_cpi_config,
            &Pubkey::from(light_sdk::constants::LIGHT_SYSTEM_PROGRAM_ID),
            Some(cpi_context_keypair),
        );

        let instruction = create_initialize_merkle_tree_instruction_registry(
            payer.pubkey(),
            merkle_tree_keypair.pubkey(),
            nullifier_queue_keypair.pubkey(),
            cpi_context_keypair.pubkey(),
            merkle_tree_config.clone(),
            queue_config.clone(),
            program_owner,
            forester,
        );
        Transaction::new_signed_with_payer(
            &[
                create_cpi_context_instruction,
                merkle_tree_account_create_ix,
                nullifier_queue_account_create_ix,
                instruction,
            ],
            Some(&payer.pubkey()),
            &vec![
                payer,
                merkle_tree_keypair,
                nullifier_queue_keypair,
                cpi_context_keypair,
            ],
            rpc.get_latest_blockhash().await?.0,
        )
    } else {
        let instruction = create_initialize_merkle_tree_instruction(
            payer.pubkey(),
            None,
            merkle_tree_keypair.pubkey(),
            nullifier_queue_keypair.pubkey(),
            merkle_tree_config.clone(),
            queue_config.clone(),
            program_owner,
            forester,
            index,
        );
        Transaction::new_signed_with_payer(
            &[
                merkle_tree_account_create_ix,
                nullifier_queue_account_create_ix,
                instruction,
            ],
            Some(&payer.pubkey()),
            &vec![payer, merkle_tree_keypair, nullifier_queue_keypair],
            rpc.get_latest_blockhash().await?.0,
        )
    };

    rpc.process_transaction(transaction).await
}