#![cfg(feature = "test-bpf")] #![allow(dead_code)] use num_traits::FromPrimitive; use std::convert::TryInto; use agsol_token_metadata::state::MasterEditionV2; use solana_program::pubkey::Pubkey; use solana_program::system_instruction; use solana_sdk::instruction::InstructionError; use solana_sdk::signer::keypair::Keypair; use solana_sdk::signer::Signer; use solana_sdk::transaction::TransactionError; use agsol_gold_contract::instruction::factory::*; use agsol_gold_contract::pda::{auction_cycle_state_seeds, auction_root_state_seeds}; use agsol_gold_contract::state::{ AuctionConfig, AuctionCycleState, AuctionDescription, AuctionRootState, BidData, CreateTokenArgs, NftData, TokenConfig, TokenData, TokenType, }; use agsol_gold_contract::AuctionContractError; use agsol_gold_contract::ID as CONTRACT_ID; use agsol_common::MaxLenString; use agsol_testbench::solana_program_test::{self, processor}; use agsol_testbench::{ Testbench, TestbenchError, TestbenchProgram, TestbenchResult, TestbenchTransactionResult, }; pub const TRANSACTION_FEE: u64 = 5000; pub const INITIAL_AUCTION_POOL_LEN: u32 = 3; pub fn to_auction_error(program_err: TransactionError) -> AuctionContractError { match program_err { TransactionError::InstructionError(_, InstructionError::Custom(code)) => { FromPrimitive::from_u32(code).unwrap() } _ => unimplemented!(), } } /* pub fn to_auction_error(testbench_result: Result) -> AuctionContractError { let program_err = testbench_result.unwrap().err().unwrap(); to_auction_error_intermediary(program_err) } */ type TestbenchResultOption = TestbenchResult>; type AuctionTransactionResult = TestbenchResult>; pub struct TestContractConfig { pub auction_owner: TestUser, pub auction_id: [u8; 32], } pub struct TestUser { pub keypair: Keypair, } impl TestUser { pub async fn new(testbench: &mut Testbench) -> TestbenchTransactionResult { let keypair = Keypair::new(); // send lamports to user let instruction = system_instruction::transfer( &testbench.payer().pubkey(), &keypair.pubkey(), 500_000_000, ); let payer = testbench.clone_payer(); testbench .process_transaction(&[instruction], &payer, None) .await .map(|transaction_result| transaction_result.map(|_| Self { keypair })) } } pub async fn warp_to_cycle_end( testbench: &mut Testbench, auction_id: [u8; 32], ) -> TestbenchResult<()> { let (_, auction_cycle_state_pubkey) = get_state_pubkeys(testbench, auction_id).await?; let auction_cycle_state = testbench .get_and_deserialize_account_data::(&auction_cycle_state_pubkey) .await?; let current_time = testbench.block_time().await?; let warp_duration = auction_cycle_state.end_time - current_time + 1; if warp_duration > 1 { testbench.warp_n_seconds(warp_duration).await?; } let current_time = testbench.block_time().await?; assert!(auction_cycle_state.end_time < current_time); Ok(()) } pub async fn get_next_child_edition( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResultOption { let nft_data = get_nft_data(testbench, auction_root_state_pubkey).await?; match nft_data { Some(data) => { let master_edition_data = testbench .get_and_deserialize_account_data::(&data.master_edition) .await?; Ok(Some(master_edition_data.supply + 1)) } None => Ok(None), } } pub async fn get_nft_data( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResultOption { let auction_root_state = testbench .get_and_deserialize_account_data::(auction_root_state_pubkey) .await?; match auction_root_state.token_config { TokenConfig::Token(_) => Ok(None), TokenConfig::Nft(nft_data) => Ok(Some(nft_data)), } } pub async fn get_token_data( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResultOption { let auction_root_state = testbench .get_and_deserialize_account_data::(auction_root_state_pubkey) .await?; match auction_root_state.token_config { TokenConfig::Token(token_data) => Ok(Some(token_data)), TokenConfig::Nft(_) => Ok(None), } } pub async fn get_current_cycle_number( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResult { let auction_root_state = testbench .get_and_deserialize_account_data::(auction_root_state_pubkey) .await?; Ok(auction_root_state.status.current_auction_cycle) } pub async fn get_top_bid( testbench: &mut Testbench, auction_cycle_state_pubkey: &Pubkey, ) -> TestbenchResultOption { let auction_cycle_state = testbench .get_and_deserialize_account_data::(auction_cycle_state_pubkey) .await?; Ok(auction_cycle_state.bid_history.get_last_element().cloned()) } pub async fn get_top_bidder_pubkey( testbench: &mut Testbench, auction_cycle_state_pubkey: &Pubkey, ) -> TestbenchResultOption { let auction_cycle_state = testbench .get_and_deserialize_account_data::(auction_cycle_state_pubkey) .await?; Ok(auction_cycle_state .bid_history .get_last_element() .map(|bid_data| bid_data.bidder_pubkey)) } pub async fn get_state_pubkeys( testbench: &mut Testbench, auction_id: [u8; 32], ) -> TestbenchResult<(Pubkey, Pubkey)> { let (auction_root_state_pubkey, _) = Pubkey::find_program_address(&auction_root_state_seeds(&auction_id), &CONTRACT_ID); let cycle_number = get_current_cycle_number(testbench, &auction_root_state_pubkey).await?; let cycle_number_bytes = cycle_number.to_le_bytes(); let (auction_cycle_state_pubkey, _) = Pubkey::find_program_address( &auction_cycle_state_seeds(&auction_root_state_pubkey, &cycle_number_bytes), &CONTRACT_ID, ); Ok((auction_root_state_pubkey, auction_cycle_state_pubkey)) } pub async fn close_cycle_transaction( testbench: &mut Testbench, payer_keypair: &Keypair, auction_id: [u8; 32], auction_owner_pubkey: &Pubkey, token_type: TokenType, ) -> AuctionTransactionResult { let (auction_root_state_pubkey, auction_cycle_state_pubkey) = get_state_pubkeys(testbench, auction_id).await?; let next_cycle_num = get_current_cycle_number(testbench, &auction_root_state_pubkey).await?; let close_auction_cycle_args = CloseAuctionCycleArgs { payer_pubkey: payer_keypair.pubkey(), auction_owner_pubkey: *auction_owner_pubkey, top_bidder_pubkey: get_top_bidder_pubkey(testbench, &auction_cycle_state_pubkey).await?, auction_id, next_cycle_num, token_type, }; let close_auction_cycle_ix = close_auction_cycle(&close_auction_cycle_args); testbench .process_transaction(&[close_auction_cycle_ix], payer_keypair, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn freeze_auction_transaction( testbench: &mut Testbench, auction_id: [u8; 32], auction_owner_keypair: &Keypair, ) -> AuctionTransactionResult { let (auction_root_state_pubkey, auction_cycle_state_pubkey) = get_state_pubkeys(testbench, auction_id).await?; let freeze_args = FreezeAuctionArgs { auction_owner_pubkey: auction_owner_keypair.pubkey(), auction_id, top_bidder_pubkey: get_top_bidder_pubkey(testbench, &auction_cycle_state_pubkey).await?, cycle_number: get_current_cycle_number(testbench, &auction_root_state_pubkey).await?, }; let freeze_instruction = freeze_auction(&freeze_args); testbench .process_transaction(&[freeze_instruction], auction_owner_keypair, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn filter_auction_transaction( testbench: &mut Testbench, auction_id: [u8; 32], filter: bool, contract_admin_keypair: &Keypair, ) -> AuctionTransactionResult { let filter_instruction = filter_auction(contract_admin_keypair.pubkey(), auction_id, filter); testbench .process_transaction(&[filter_instruction], contract_admin_keypair, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn verify_auction_transaction( testbench: &mut Testbench, auction_id: [u8; 32], contract_admin_keypair: &Keypair, ) -> AuctionTransactionResult { let verify_args = VerifyAuctionArgs { contract_admin_pubkey: contract_admin_keypair.pubkey(), auction_id, }; let verify_instruction = verify_auction(&verify_args); testbench .process_transaction(&[verify_instruction], contract_admin_keypair, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn claim_funds_transaction( testbench: &mut Testbench, auction_id: [u8; 32], auction_owner: &Keypair, amount: u64, ) -> AuctionTransactionResult { let (auction_root_state_pubkey, _) = Pubkey::find_program_address(&auction_root_state_seeds(&auction_id), &CONTRACT_ID); let claim_funds_args = ClaimFundsArgs { auction_owner_pubkey: auction_owner.pubkey(), auction_id, cycle_number: get_current_cycle_number(testbench, &auction_root_state_pubkey).await?, amount, }; let claim_funds_ix = claim_funds(&claim_funds_args); testbench .process_transaction(&[claim_funds_ix], auction_owner, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn place_bid_transaction( testbench: &mut Testbench, auction_id: [u8; 32], user_keypair: &Keypair, amount: u64, ) -> AuctionTransactionResult { let (auction_root_state_pubkey, auction_cycle_state_pubkey) = get_state_pubkeys(testbench, auction_id).await?; let place_bid_args = PlaceBidArgs { user_main_pubkey: user_keypair.pubkey(), auction_id, cycle_number: get_current_cycle_number(testbench, &auction_root_state_pubkey).await?, top_bidder_pubkey: get_top_bidder_pubkey(testbench, &auction_cycle_state_pubkey).await?, amount, }; let bid_instruction = place_bid(&place_bid_args); testbench .process_transaction(&[bid_instruction], user_keypair, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn initialize_new_auction_custom( testbench: &mut Testbench, auction_owner: &Keypair, auction_config: &AuctionConfig, auction_id: [u8; 32], create_token_args: CreateTokenArgs, ) -> AuctionTransactionResult { let initialize_auction_args = InitializeAuctionArgs { auction_owner_pubkey: auction_owner.pubkey(), auction_id, auction_config: *auction_config, auction_name: auction_id, auction_description: AuctionDescription { description: MaxLenString::try_from("Cool description").unwrap(), socials: vec![MaxLenString::try_from("https://www.gold.xyz").unwrap()] .try_into() .unwrap(), goal_treasury_amount: Some(420_000_000_000), }, create_token_args, auction_start_timestamp: None, }; let instruction = initialize_auction(&initialize_auction_args); testbench .process_transaction(&[instruction], auction_owner, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn initialize_new_auction( testbench: &mut Testbench, auction_owner: &Keypair, auction_config: &AuctionConfig, auction_id: [u8; 32], token_type: TokenType, ) -> AuctionTransactionResult { let initialize_auction_args = InitializeAuctionArgs::new_test( auction_owner.pubkey(), *auction_config, auction_id, token_type, ); let instruction = initialize_auction(&initialize_auction_args); testbench .process_transaction(&[instruction], auction_owner, None) .await .map(|transaction_result| transaction_result.map_err(to_auction_error)) } pub async fn get_auction_cycle_pubkey( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResult { let auction_root_state = testbench .get_and_deserialize_account_data::(auction_root_state_pubkey) .await?; let cycle_number_bytes = auction_root_state .status .current_auction_cycle .to_le_bytes(); let (auction_cycle_state_pubkey, _) = Pubkey::find_program_address( &auction_cycle_state_seeds(auction_root_state_pubkey, &cycle_number_bytes), &CONTRACT_ID, ); Ok(auction_cycle_state_pubkey) } pub async fn is_existing_account( testbench: &mut Testbench, account_pubkey: &Pubkey, ) -> TestbenchResult { let account_query = testbench.get_account(account_pubkey).await; match account_query { Err(err) => match err { TestbenchError::AccountNotFound => Ok(false), _ => Err(err), }, Ok(_) => Ok(true), } } pub async fn get_auction_cycle_state( testbench: &mut Testbench, auction_root_state_pubkey: &Pubkey, ) -> TestbenchResult<(Pubkey, AuctionCycleState)> { let auction_cycle_state_pubkey = get_auction_cycle_pubkey(testbench, auction_root_state_pubkey).await?; let auction_cycle_state = testbench .get_and_deserialize_account_data::(&auction_cycle_state_pubkey) .await?; Ok((auction_cycle_state_pubkey, auction_cycle_state)) } pub async fn testbench_setup() -> TestbenchTransactionResult<(Testbench, TestUser)> { let program_id = agsol_gold_contract::id(); let testbench_program = TestbenchProgram { name: "agsol_gold_contract", id: program_id, process_instruction: processor!(agsol_gold_contract::processor::process), }; // load metadata program binary let meta_program_id = agsol_token_metadata::id(); let meta_program = TestbenchProgram { name: "spl_token_metadata", id: meta_program_id, process_instruction: None, }; let mut testbench = Testbench::new(&[testbench_program, meta_program]).await?; let initialize_contract_args = InitializeContractArgs { contract_admin: testbench.payer().pubkey(), withdraw_authority: testbench.payer().pubkey(), initial_auction_pool_len: INITIAL_AUCTION_POOL_LEN, }; let init_contract_ix = initialize_contract(&initialize_contract_args); let result = testbench .process_transaction(&[init_contract_ix], &testbench.clone_payer(), None) .await; // TODO: unwrap here is is somewhat ok because it does not include own contract code // However, this is includes a second process_transaction call with potentially // different transaction error let auction_owner = TestUser::new(&mut testbench).await?.unwrap(); result.map(|transaction_result| transaction_result.map(|_| (testbench, auction_owner))) }