From 8730dbbb881fc3201ef000ee70cc3fc1d6813f9a Mon Sep 17 00:00:00 2001 From: Jon C Date: Tue, 26 Nov 2024 14:04:13 +0100 Subject: [PATCH] transaction-status: Add confidential transfer tests (#3786) #### Problem The new parsers for the confidential transfer extensions in #3431 don't have any tests, which is dangerous because they involve indexing directly into arrays, which can panic at runtime. #### Summary of changes Add tests for all the instructions involving proofs, which have the more complicated parsing logic. Specifically, it's testing to make sure that nothing panics. These tests actually uncovered a bug that triggered a panic for instructions with just barely enough accounts and also the instructions sysvar. --- Cargo.lock | 2 + Cargo.toml | 1 + transaction-status/Cargo.toml | 4 + .../extension/confidential_mint_burn.rs | 222 +++++++++++- .../extension/confidential_transfer.rs | 341 +++++++++++++++++- .../extension/confidential_transfer_fee.rs | 93 +++++ 6 files changed, 647 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5dfc4f3227fe0..19ef30409ccdb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9275,6 +9275,7 @@ dependencies = [ "bincode", "borsh 1.5.3", "bs58", + "bytemuck", "lazy_static", "log", "serde", @@ -9287,6 +9288,7 @@ dependencies = [ "spl-memo", "spl-token", "spl-token-2022", + "spl-token-confidential-transfer-proof-extraction", "spl-token-group-interface", "spl-token-metadata-interface", "thiserror 2.0.3", diff --git a/Cargo.toml b/Cargo.toml index b402283e560a5e..7634a7baeb4abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -589,6 +589,7 @@ spl-memo = "=6.0.0" spl-pod = "=0.5.0" spl-token = "=7.0.0" spl-token-2022 = "=6.0.0" +spl-token-confidential-transfer-proof-extraction = "0.2.0" spl-token-group-interface = "=0.5.0" spl-token-metadata-interface = "=0.6.0" static_assertions = "1.1.0" diff --git a/transaction-status/Cargo.toml b/transaction-status/Cargo.toml index 3d913df0864d48..c4de162f9a8e7f 100644 --- a/transaction-status/Cargo.toml +++ b/transaction-status/Cargo.toml @@ -31,5 +31,9 @@ spl-token-group-interface = { workspace = true } spl-token-metadata-interface = { workspace = true } thiserror = { workspace = true } +[dev-dependencies] +bytemuck = { workspace = true } +spl-token-confidential-transfer-proof-extraction = { workspace = true } + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/transaction-status/src/parse_token/extension/confidential_mint_burn.rs b/transaction-status/src/parse_token/extension/confidential_mint_burn.rs index 9eba0158460555..93e44a773700c9 100644 --- a/transaction-status/src/parse_token/extension/confidential_mint_burn.rs +++ b/transaction-status/src/parse_token/extension/confidential_mint_burn.rs @@ -109,9 +109,10 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction( }); let mut offset = 2; let map = value.as_object_mut().unwrap(); - if mint_data.equality_proof_instruction_offset != 0 - || mint_data.ciphertext_validity_proof_instruction_offset != 0 - || mint_data.range_proof_instruction_offset != 0 + if offset < account_indexes.len() - 1 + && (mint_data.equality_proof_instruction_offset != 0 + || mint_data.ciphertext_validity_proof_instruction_offset != 0 + || mint_data.range_proof_instruction_offset != 0) { map.insert( "instructionsSysvar".to_string(), @@ -189,9 +190,10 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction( }); let mut offset = 2; let map = value.as_object_mut().unwrap(); - if burn_data.equality_proof_instruction_offset != 0 - || burn_data.ciphertext_validity_proof_instruction_offset != 0 - || burn_data.range_proof_instruction_offset != 0 + if offset < account_indexes.len() - 1 + && (burn_data.equality_proof_instruction_offset != 0 + || burn_data.ciphertext_validity_proof_instruction_offset != 0 + || burn_data.range_proof_instruction_offset != 0) { map.insert( "instructionsSysvar".to_string(), @@ -254,3 +256,211 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction( } } } + +#[cfg(test)] +mod test { + use { + super::*, + bytemuck::Zeroable, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + }, + spl_token_2022::{ + extension::confidential_mint_burn::instruction::{ + confidential_burn_with_split_proofs, confidential_mint_with_split_proofs, + initialize_mint, + }, + solana_program::message::Message, + solana_zk_sdk::{ + encryption::{ + auth_encryption::AeCiphertext, + elgamal::ElGamalPubkey, + pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey}, + }, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData, + }, + }, + }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, + std::num::NonZero, + }; + + fn check_no_panic(mut instruction: Instruction) { + let account_meta = AccountMeta::new_readonly(Pubkey::new_unique(), false); + for i in 0..20 { + instruction.accounts = vec![account_meta.clone(); i]; + let message = Message::new(&[instruction.clone()], None); + let compiled_instruction = &message.instructions[0]; + let _ = parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None), + ); + } + } + + #[test] + fn test_initialize() { + let instruction = initialize_mint( + &spl_token_2022::id(), + &Pubkey::new_unique(), + PodElGamalPubkey::default(), + PodAeCiphertext::default(), + ) + .unwrap(); + check_no_panic(instruction); + } + + #[test] + fn test_update() { + let instruction = update_decryptable_supply( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &[], + AeCiphertext::default(), + ) + .unwrap(); + check_no_panic(instruction); + } + + #[test] + fn test_rotate() { + for location in [ + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCiphertextEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ] { + let instructions = rotate_supply_elgamal_pubkey( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &[], + ElGamalPubkey::default(), + location, + ) + .unwrap(); + check_no_panic(instructions[0].clone()); + } + } + + #[test] + fn test_mint() { + for (equality_proof_location, ciphertext_validity_proof_location, range_proof_location) in [ + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::InstructionData( + &BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(), + ), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::InstructionData(&BatchedRangeProofU128Data::zeroed()), + ), + ), + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ), + ( + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ), + ] { + let instructions = confidential_mint_with_split_proofs( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + None, + &Pubkey::new_unique(), + &[], + equality_proof_location, + ciphertext_validity_proof_location, + range_proof_location, + AeCiphertext::default(), + ) + .unwrap(); + check_no_panic(instructions[0].clone()); + } + } + + #[test] + fn test_burn() { + for (equality_proof_location, ciphertext_validity_proof_location, range_proof_location) in [ + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::InstructionData( + &BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(), + ), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::InstructionData(&BatchedRangeProofU128Data::zeroed()), + ), + ), + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ), + ( + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ), + ] { + let instructions = confidential_burn_with_split_proofs( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + None, + PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + equality_proof_location, + ciphertext_validity_proof_location, + range_proof_location, + ) + .unwrap(); + check_no_panic(instructions[0].clone()); + } + } +} diff --git a/transaction-status/src/parse_token/extension/confidential_transfer.rs b/transaction-status/src/parse_token/extension/confidential_transfer.rs index 4e15620cc6c187..b9bf877c8a2498 100644 --- a/transaction-status/src/parse_token/extension/confidential_transfer.rs +++ b/transaction-status/src/parse_token/extension/confidential_transfer.rs @@ -215,8 +215,9 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( }); let mut offset = 3; let map = value.as_object_mut().unwrap(); - if withdrawal_data.equality_proof_instruction_offset != 0 - || withdrawal_data.range_proof_instruction_offset != 0 + if offset < account_indexes.len() - 1 + && (withdrawal_data.equality_proof_instruction_offset != 0 + || withdrawal_data.range_proof_instruction_offset != 0) { map.insert( "instructionsSysvar".to_string(), @@ -283,9 +284,10 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( }); let mut offset = 3; let map = value.as_object_mut().unwrap(); - if transfer_data.equality_proof_instruction_offset != 0 - || transfer_data.ciphertext_validity_proof_instruction_offset != 0 - || transfer_data.range_proof_instruction_offset != 0 + if offset < account_indexes.len() - 1 + && (transfer_data.equality_proof_instruction_offset != 0 + || transfer_data.ciphertext_validity_proof_instruction_offset != 0 + || transfer_data.range_proof_instruction_offset != 0) { map.insert( "instructionsSysvar".to_string(), @@ -377,11 +379,12 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( let mut offset = 3; let map = value.as_object_mut().unwrap(); - if equality_proof_instruction_offset != 0 - || transfer_amount_ciphertext_validity_proof_instruction_offset != 0 - || fee_ciphertext_validity_proof_instruction_offset != 0 - || fee_sigma_proof_instruction_offset != 0 - || range_proof_instruction_offset != 0 + if offset < account_indexes.len() - 1 + && (equality_proof_instruction_offset != 0 + || transfer_amount_ciphertext_validity_proof_instruction_offset != 0 + || fee_ciphertext_validity_proof_instruction_offset != 0 + || fee_sigma_proof_instruction_offset != 0 + || range_proof_instruction_offset != 0) { map.insert( "instructionsSysvar".to_string(), @@ -588,3 +591,321 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( } } } + +#[cfg(test)] +mod test { + use { + super::*, + bytemuck::Zeroable, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + }, + spl_token_2022::{ + extension::confidential_transfer::instruction::{ + initialize_mint, inner_configure_account, inner_empty_account, update_mint, + }, + solana_program::message::Message, + solana_zk_sdk::{ + encryption::pod::auth_encryption::PodAeCiphertext, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofData, ZeroCiphertextProofData, + }, + }, + }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, + std::num::NonZero, + }; + + fn check_no_panic(mut instruction: Instruction) { + let account_meta = AccountMeta::new_readonly(Pubkey::new_unique(), false); + for i in 0..20 { + instruction.accounts = vec![account_meta.clone(); i]; + let message = Message::new(&[instruction.clone()], None); + let compiled_instruction = &message.instructions[0]; + let _ = parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None), + ); + } + } + + #[test] + fn test_initialize() { + let instruction = initialize_mint( + &spl_token_2022::id(), + &Pubkey::new_unique(), + Some(Pubkey::new_unique()), + true, + None, + ) + .unwrap(); + check_no_panic(instruction); + } + + #[test] + fn test_approve() { + let instruction = approve_account( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &[], + ) + .unwrap(); + check_no_panic(instruction); + } + + #[test] + fn test_update() { + let instruction = update_mint( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &[], + true, + None, + ) + .unwrap(); + check_no_panic(instruction); + } + + #[test] + fn test_configure() { + for location in [ + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&PubkeyValidityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ] { + let instruction = inner_configure_account( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + PodAeCiphertext::default(), + 10_000, + &Pubkey::new_unique(), + &[], + location, + ) + .unwrap(); + check_no_panic(instruction); + } + } + + #[test] + fn test_empty_account() { + for location in [ + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&ZeroCiphertextProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ] { + let instruction = inner_empty_account( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &[], + location, + ) + .unwrap(); + check_no_panic(instruction); + } + } + + #[test] + fn test_withdraw() { + for (equality_proof_location, range_proof_location) in [ + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::InstructionData(&BatchedRangeProofU64Data::zeroed()), + ), + ), + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ), + ( + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ), + ] { + let instruction = inner_withdraw( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + 1, + 2, + PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + equality_proof_location, + range_proof_location, + ) + .unwrap(); + check_no_panic(instruction); + } + } + + #[test] + fn test_transfer() { + for (equality_proof_location, ciphertext_validity_proof_location, range_proof_location) in [ + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::InstructionData( + &BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(), + ), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::InstructionData(&BatchedRangeProofU128Data::zeroed()), + ), + ), + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ), + ( + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ), + ] { + let instruction = inner_transfer( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + equality_proof_location, + ciphertext_validity_proof_location, + range_proof_location, + ) + .unwrap(); + check_no_panic(instruction); + } + } + + #[test] + fn test_transfer_with_fee() { + for ( + equality_proof_location, + transfer_amount_ciphertext_validity_proof_location, + fee_sigma_proof_location, + fee_ciphertext_validity_proof_location, + range_proof_location, + ) in [ + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::InstructionData( + &BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(), + ), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::InstructionData(&PercentageWithCapProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(4).unwrap(), + ProofData::InstructionData( + &BatchedGroupedCiphertext2HandlesValidityProofData::zeroed(), + ), + ), + ProofLocation::InstructionOffset( + NonZero::new(5).unwrap(), + ProofData::InstructionData(&BatchedRangeProofU256Data::zeroed()), + ), + ), + ( + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(2).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(3).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(4).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::InstructionOffset( + NonZero::new(5).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ), + ( + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ), + ] { + let instruction = inner_transfer_with_fee( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + equality_proof_location, + transfer_amount_ciphertext_validity_proof_location, + fee_sigma_proof_location, + fee_ciphertext_validity_proof_location, + range_proof_location, + ) + .unwrap(); + check_no_panic(instruction); + } + } +} diff --git a/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs index 8370038d508a40..365623946a3518 100644 --- a/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs +++ b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs @@ -203,3 +203,96 @@ pub(in crate::parse_token) fn parse_confidential_transfer_fee_instruction( } } } + +#[cfg(test)] +mod test { + use { + super::*, + bytemuck::Zeroable, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + }, + spl_token_2022::{ + extension::confidential_transfer_fee::instruction::{ + inner_withdraw_withheld_tokens_from_accounts, + inner_withdraw_withheld_tokens_from_mint, + }, + solana_program::message::Message, + solana_zk_sdk::{ + encryption::pod::auth_encryption::PodAeCiphertext, + zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData, + }, + }, + spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation}, + std::num::NonZero, + }; + + fn check_no_panic(mut instruction: Instruction) { + let account_meta = AccountMeta::new_readonly(Pubkey::new_unique(), false); + for i in 0..20 { + instruction.accounts = vec![account_meta.clone(); i]; + let message = Message::new(&[instruction.clone()], None); + let compiled_instruction = &message.instructions[0]; + let _ = parse_token( + compiled_instruction, + &AccountKeys::new(&message.account_keys, None), + ); + } + } + + #[test] + fn test_withdraw_from_accounts() { + for location in [ + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCiphertextEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ] { + let instruction = inner_withdraw_withheld_tokens_from_accounts( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + &[&Pubkey::new_unique(), &Pubkey::new_unique()], + location, + ) + .unwrap(); + check_no_panic(instruction); + } + } + + #[test] + fn test_withdraw_from_mint() { + for location in [ + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::InstructionData(&CiphertextCiphertextEqualityProofData::zeroed()), + ), + ProofLocation::InstructionOffset( + NonZero::new(1).unwrap(), + ProofData::RecordAccount(&Pubkey::new_unique(), 0), + ), + ProofLocation::ContextStateAccount(&Pubkey::new_unique()), + ] { + let instruction = inner_withdraw_withheld_tokens_from_mint( + &spl_token_2022::id(), + &Pubkey::new_unique(), + &Pubkey::new_unique(), + &PodAeCiphertext::default(), + &Pubkey::new_unique(), + &[], + location, + ) + .unwrap(); + check_no_panic(instruction); + } + } +}