Skip to content

Commit 8730dbb

Browse files
authored
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.
1 parent 191cf4c commit 8730dbb

File tree

6 files changed

+647
-16
lines changed

6 files changed

+647
-16
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ spl-memo = "=6.0.0"
589589
spl-pod = "=0.5.0"
590590
spl-token = "=7.0.0"
591591
spl-token-2022 = "=6.0.0"
592+
spl-token-confidential-transfer-proof-extraction = "0.2.0"
592593
spl-token-group-interface = "=0.5.0"
593594
spl-token-metadata-interface = "=0.6.0"
594595
static_assertions = "1.1.0"

transaction-status/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@ spl-token-group-interface = { workspace = true }
3131
spl-token-metadata-interface = { workspace = true }
3232
thiserror = { workspace = true }
3333

34+
[dev-dependencies]
35+
bytemuck = { workspace = true }
36+
spl-token-confidential-transfer-proof-extraction = { workspace = true }
37+
3438
[package.metadata.docs.rs]
3539
targets = ["x86_64-unknown-linux-gnu"]

transaction-status/src/parse_token/extension/confidential_mint_burn.rs

Lines changed: 216 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,10 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction(
109109
});
110110
let mut offset = 2;
111111
let map = value.as_object_mut().unwrap();
112-
if mint_data.equality_proof_instruction_offset != 0
113-
|| mint_data.ciphertext_validity_proof_instruction_offset != 0
114-
|| mint_data.range_proof_instruction_offset != 0
112+
if offset < account_indexes.len() - 1
113+
&& (mint_data.equality_proof_instruction_offset != 0
114+
|| mint_data.ciphertext_validity_proof_instruction_offset != 0
115+
|| mint_data.range_proof_instruction_offset != 0)
115116
{
116117
map.insert(
117118
"instructionsSysvar".to_string(),
@@ -189,9 +190,10 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction(
189190
});
190191
let mut offset = 2;
191192
let map = value.as_object_mut().unwrap();
192-
if burn_data.equality_proof_instruction_offset != 0
193-
|| burn_data.ciphertext_validity_proof_instruction_offset != 0
194-
|| burn_data.range_proof_instruction_offset != 0
193+
if offset < account_indexes.len() - 1
194+
&& (burn_data.equality_proof_instruction_offset != 0
195+
|| burn_data.ciphertext_validity_proof_instruction_offset != 0
196+
|| burn_data.range_proof_instruction_offset != 0)
195197
{
196198
map.insert(
197199
"instructionsSysvar".to_string(),
@@ -254,3 +256,211 @@ pub(in crate::parse_token) fn parse_confidential_mint_burn_instruction(
254256
}
255257
}
256258
}
259+
260+
#[cfg(test)]
261+
mod test {
262+
use {
263+
super::*,
264+
bytemuck::Zeroable,
265+
solana_sdk::{
266+
instruction::{AccountMeta, Instruction},
267+
pubkey::Pubkey,
268+
},
269+
spl_token_2022::{
270+
extension::confidential_mint_burn::instruction::{
271+
confidential_burn_with_split_proofs, confidential_mint_with_split_proofs,
272+
initialize_mint,
273+
},
274+
solana_program::message::Message,
275+
solana_zk_sdk::{
276+
encryption::{
277+
auth_encryption::AeCiphertext,
278+
elgamal::ElGamalPubkey,
279+
pod::{auth_encryption::PodAeCiphertext, elgamal::PodElGamalPubkey},
280+
},
281+
zk_elgamal_proof_program::proof_data::{
282+
BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
283+
CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData,
284+
},
285+
},
286+
},
287+
spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation},
288+
std::num::NonZero,
289+
};
290+
291+
fn check_no_panic(mut instruction: Instruction) {
292+
let account_meta = AccountMeta::new_readonly(Pubkey::new_unique(), false);
293+
for i in 0..20 {
294+
instruction.accounts = vec![account_meta.clone(); i];
295+
let message = Message::new(&[instruction.clone()], None);
296+
let compiled_instruction = &message.instructions[0];
297+
let _ = parse_token(
298+
compiled_instruction,
299+
&AccountKeys::new(&message.account_keys, None),
300+
);
301+
}
302+
}
303+
304+
#[test]
305+
fn test_initialize() {
306+
let instruction = initialize_mint(
307+
&spl_token_2022::id(),
308+
&Pubkey::new_unique(),
309+
PodElGamalPubkey::default(),
310+
PodAeCiphertext::default(),
311+
)
312+
.unwrap();
313+
check_no_panic(instruction);
314+
}
315+
316+
#[test]
317+
fn test_update() {
318+
let instruction = update_decryptable_supply(
319+
&spl_token_2022::id(),
320+
&Pubkey::new_unique(),
321+
&Pubkey::new_unique(),
322+
&[],
323+
AeCiphertext::default(),
324+
)
325+
.unwrap();
326+
check_no_panic(instruction);
327+
}
328+
329+
#[test]
330+
fn test_rotate() {
331+
for location in [
332+
ProofLocation::InstructionOffset(
333+
NonZero::new(1).unwrap(),
334+
ProofData::InstructionData(&CiphertextCiphertextEqualityProofData::zeroed()),
335+
),
336+
ProofLocation::InstructionOffset(
337+
NonZero::new(1).unwrap(),
338+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
339+
),
340+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
341+
] {
342+
let instructions = rotate_supply_elgamal_pubkey(
343+
&spl_token_2022::id(),
344+
&Pubkey::new_unique(),
345+
&Pubkey::new_unique(),
346+
&[],
347+
ElGamalPubkey::default(),
348+
location,
349+
)
350+
.unwrap();
351+
check_no_panic(instructions[0].clone());
352+
}
353+
}
354+
355+
#[test]
356+
fn test_mint() {
357+
for (equality_proof_location, ciphertext_validity_proof_location, range_proof_location) in [
358+
(
359+
ProofLocation::InstructionOffset(
360+
NonZero::new(1).unwrap(),
361+
ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()),
362+
),
363+
ProofLocation::InstructionOffset(
364+
NonZero::new(2).unwrap(),
365+
ProofData::InstructionData(
366+
&BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(),
367+
),
368+
),
369+
ProofLocation::InstructionOffset(
370+
NonZero::new(3).unwrap(),
371+
ProofData::InstructionData(&BatchedRangeProofU128Data::zeroed()),
372+
),
373+
),
374+
(
375+
ProofLocation::InstructionOffset(
376+
NonZero::new(1).unwrap(),
377+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
378+
),
379+
ProofLocation::InstructionOffset(
380+
NonZero::new(2).unwrap(),
381+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
382+
),
383+
ProofLocation::InstructionOffset(
384+
NonZero::new(3).unwrap(),
385+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
386+
),
387+
),
388+
(
389+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
390+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
391+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
392+
),
393+
] {
394+
let instructions = confidential_mint_with_split_proofs(
395+
&spl_token_2022::id(),
396+
&Pubkey::new_unique(),
397+
&Pubkey::new_unique(),
398+
None,
399+
&Pubkey::new_unique(),
400+
&[],
401+
equality_proof_location,
402+
ciphertext_validity_proof_location,
403+
range_proof_location,
404+
AeCiphertext::default(),
405+
)
406+
.unwrap();
407+
check_no_panic(instructions[0].clone());
408+
}
409+
}
410+
411+
#[test]
412+
fn test_burn() {
413+
for (equality_proof_location, ciphertext_validity_proof_location, range_proof_location) in [
414+
(
415+
ProofLocation::InstructionOffset(
416+
NonZero::new(1).unwrap(),
417+
ProofData::InstructionData(&CiphertextCommitmentEqualityProofData::zeroed()),
418+
),
419+
ProofLocation::InstructionOffset(
420+
NonZero::new(2).unwrap(),
421+
ProofData::InstructionData(
422+
&BatchedGroupedCiphertext3HandlesValidityProofData::zeroed(),
423+
),
424+
),
425+
ProofLocation::InstructionOffset(
426+
NonZero::new(3).unwrap(),
427+
ProofData::InstructionData(&BatchedRangeProofU128Data::zeroed()),
428+
),
429+
),
430+
(
431+
ProofLocation::InstructionOffset(
432+
NonZero::new(1).unwrap(),
433+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
434+
),
435+
ProofLocation::InstructionOffset(
436+
NonZero::new(2).unwrap(),
437+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
438+
),
439+
ProofLocation::InstructionOffset(
440+
NonZero::new(3).unwrap(),
441+
ProofData::RecordAccount(&Pubkey::new_unique(), 0),
442+
),
443+
),
444+
(
445+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
446+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
447+
ProofLocation::ContextStateAccount(&Pubkey::new_unique()),
448+
),
449+
] {
450+
let instructions = confidential_burn_with_split_proofs(
451+
&spl_token_2022::id(),
452+
&Pubkey::new_unique(),
453+
&Pubkey::new_unique(),
454+
None,
455+
PodAeCiphertext::default(),
456+
&Pubkey::new_unique(),
457+
&[],
458+
equality_proof_location,
459+
ciphertext_validity_proof_location,
460+
range_proof_location,
461+
)
462+
.unwrap();
463+
check_no_panic(instructions[0].clone());
464+
}
465+
}
466+
}

0 commit comments

Comments
 (0)