#![cfg(feature = "test-sbf")]
pub mod setup;
use solana_program_test::tokio;
use solana_sdk::{
keccak::hash, pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction,
};
use spl_merkle_tree_reference::{MerkleTree, EMPTY};
use tensor_whitelist::{
accounts::{MintProofV2, WhitelistV2},
instructions::{CloseMintProofV2, InitUpdateMintProofV2, InitUpdateMintProofV2InstructionArgs},
types::{Condition, Mode},
};
use crate::setup::{
airdrop, fetch, mint_metaplex_nft, program_context, setup_default_whitelist_v2, DirtyClone,
TestMintMetaplexNftInputs, TestWhitelistV2Inputs, ONE_SOL_LAMPORTS,
};
#[tokio::test]
async fn create_and_close_mint_proof_v2() {
let mut context = program_context().await;
let payer = Keypair::new();
let update_authority = Keypair::new();
airdrop(&mut context, &payer.pubkey(), ONE_SOL_LAMPORTS)
.await
.unwrap();
airdrop(&mut context, &update_authority.pubkey(), ONE_SOL_LAMPORTS)
.await
.unwrap();
let inputs = TestMintMetaplexNftInputs {
payer: None, update_authority: update_authority.dirty_clone(),
owner: None, };
let nft = mint_metaplex_nft(&mut context, inputs).await;
let leaf = hash(nft.mint.as_ref());
let mut leaves = vec![leaf.0];
leaves.push(hash(&EMPTY).0);
leaves.sort();
let index = leaves.iter().position(|x| x == &leaf.0).unwrap();
let tree = MerkleTree::new(&leaves);
let root = tree.get_root();
let proof = tree.get_proof_of_leaf(index);
let conditions = vec![Condition {
mode: Mode::MerkleTree,
value: Pubkey::new_from_array(root), }];
let inputs = TestWhitelistV2Inputs {
update_authority: update_authority.dirty_clone(),
conditions: Some(conditions.clone()),
..TestWhitelistV2Inputs::default()
};
let test_whitelist = setup_default_whitelist_v2(&mut context, inputs).await;
let whitelist_data: WhitelistV2 = fetch(&test_whitelist.whitelist, &mut context)
.await
.unwrap();
assert_eq!(whitelist_data.conditions.len(), conditions.len());
assert_eq!(whitelist_data.uuid, test_whitelist.uuid);
assert_eq!(whitelist_data.namespace, test_whitelist.namespace);
assert_eq!(whitelist_data.update_authority, update_authority.pubkey());
context.warp_to_slot(100).unwrap();
let args = InitUpdateMintProofV2InstructionArgs {
proof: proof.clone(),
};
let mint_proof = MintProofV2::find_pda(&nft.mint, &test_whitelist.whitelist).0;
let ix = InitUpdateMintProofV2 {
whitelist: test_whitelist.whitelist,
payer: payer.pubkey(),
mint: nft.mint,
mint_proof,
system_program: solana_program::system_program::ID,
}
.instruction(args);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&context.payer.pubkey()),
&[&context.payer, &payer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();
let mint_proof_data: MintProofV2 = fetch(&mint_proof, &mut context).await.unwrap();
assert!(mint_proof_data.creation_slot >= 100);
assert_eq!(mint_proof_data.payer, payer.pubkey());
assert_eq!(&mint_proof_data.proof[0..proof.len()], proof.as_slice());
assert_eq!(mint_proof_data.proof_len, proof.len() as u8);
let payer_balance_before = context
.banks_client
.get_balance(payer.pubkey())
.await
.unwrap();
let ix = CloseMintProofV2 {
payer: payer.pubkey(),
signer: payer.pubkey(),
mint_proof,
system_program: solana_program::system_program::ID,
}
.instruction();
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&context.payer.pubkey()),
&[&context.payer, &payer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();
let err = fetch::<MintProofV2>(&mint_proof, &mut context)
.await
.unwrap_err();
assert_eq!(err.to_string(), "client error: Account not found");
let payer_balance_after = context
.banks_client
.get_balance(payer.pubkey())
.await
.unwrap();
let rent = context.banks_client.get_rent().await.unwrap();
let mint_proof_v2_rent = rent.minimum_balance(945);
assert!((payer_balance_after - payer_balance_before) == mint_proof_v2_rent);
}
#[tokio::test]
async fn closing_refunds_signer_after_100_slots() {
let mut context = program_context().await;
let payer = Keypair::new();
let signer = Keypair::new();
let update_authority = Keypair::new();
airdrop(&mut context, &payer.pubkey(), ONE_SOL_LAMPORTS)
.await
.unwrap();
airdrop(&mut context, &update_authority.pubkey(), ONE_SOL_LAMPORTS)
.await
.unwrap();
let inputs = TestMintMetaplexNftInputs {
payer: Some(payer.dirty_clone()),
update_authority: update_authority.dirty_clone(),
owner: None, };
let nft = mint_metaplex_nft(&mut context, inputs).await;
let leaf = hash(nft.mint.as_ref());
let mut leaves = vec![leaf.0];
leaves.push(hash(&EMPTY).0);
leaves.sort();
let index = leaves.iter().position(|x| x == &leaf.0).unwrap();
let tree = MerkleTree::new(&leaves);
let root = tree.get_root();
let proof = tree.get_proof_of_leaf(index);
let conditions = vec![Condition {
mode: Mode::MerkleTree,
value: Pubkey::new_from_array(root), }];
let inputs = TestWhitelistV2Inputs {
update_authority: update_authority.dirty_clone(),
conditions: Some(conditions.clone()),
..TestWhitelistV2Inputs::default()
};
let test_whitelist = setup_default_whitelist_v2(&mut context, inputs).await;
let whitelist_data: WhitelistV2 = fetch(&test_whitelist.whitelist, &mut context)
.await
.unwrap();
assert_eq!(whitelist_data.conditions.len(), conditions.len());
assert_eq!(whitelist_data.uuid, test_whitelist.uuid);
assert_eq!(whitelist_data.namespace, test_whitelist.namespace);
assert_eq!(whitelist_data.update_authority, update_authority.pubkey());
context.warp_to_slot(100).unwrap();
let args = InitUpdateMintProofV2InstructionArgs {
proof: proof.clone(),
};
let mint_proof = MintProofV2::find_pda(&nft.mint, &test_whitelist.whitelist).0;
let ix = InitUpdateMintProofV2 {
whitelist: test_whitelist.whitelist,
payer: payer.pubkey(), mint: nft.mint,
mint_proof,
system_program: solana_program::system_program::ID,
}
.instruction(args);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&context.payer.pubkey()),
&[&context.payer, &payer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();
let mint_proof_data: MintProofV2 = fetch(&mint_proof, &mut context).await.unwrap();
assert!(mint_proof_data.creation_slot >= 100);
assert_eq!(mint_proof_data.payer, payer.pubkey());
assert_eq!(&mint_proof_data.proof[0..proof.len()], proof.as_slice());
assert_eq!(mint_proof_data.proof_len, proof.len() as u8);
context.warp_to_slot(201).unwrap();
let signer_balance = context
.banks_client
.get_balance(signer.pubkey())
.await
.unwrap();
assert!(signer_balance == 0);
let ix = CloseMintProofV2 {
payer: payer.pubkey(), signer: signer.pubkey(), mint_proof,
system_program: solana_program::system_program::ID,
}
.instruction();
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&context.payer.pubkey()),
&[&context.payer, &signer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();
let err = fetch::<MintProofV2>(&mint_proof, &mut context)
.await
.unwrap_err();
assert_eq!(err.to_string(), "client error: Account not found");
let signer_balance = context
.banks_client
.get_balance(signer.pubkey())
.await
.unwrap();
let rent = context.banks_client.get_rent().await.unwrap();
let mint_proof_v2_rent = rent.minimum_balance(945);
assert!(signer_balance == mint_proof_v2_rent);
}