#![cfg(feature = "test-bpf")] pub mod common; pub mod utils; use common::*; use utils::{ helpers::{ assert_error_ignoring_io_error_in_ci, default_scopes, unwrap_ignoring_io_error_in_ci, DirtyClone, }, setup_functions::*, }; use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas}; use mpl_testing_utils::{ solana::{airdrop, create_associated_token_account, transfer}, utils::Metadata, }; use mpl_token_metadata::state::Creator; use mtly_auction_house::pda::find_auctioneer_pda; use solana_sdk::{ account::Account as SolanaAccount, commitment_config::CommitmentLevel, signer::Signer, }; use std::assert_eq; use solana_program::{ instruction::{Instruction, InstructionError}, system_program, sysvar, }; use mtly_auction_house::{ pda::{find_escrow_payment_address, find_program_as_signer_address, find_trade_state_address}, receipt::{BidReceipt, ListingReceipt, PurchaseReceipt}, }; use solana_program::program_pack::Pack; use solana_sdk::{ signature::Keypair, transaction::{Transaction, TransactionError}, }; use spl_associated_token_account::get_associated_token_address; use spl_token::state::Account; use mpl_token_metadata::{ pda::find_token_record_account, state::{PrintSupply, TokenStandard}, }; #[tokio::test] async fn execute_sale_existing_token_account_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn auctioneer_execute_sale_pnft_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let payer = context.payer.dirty_clone(); let (rule_set, auth_data) = create_sale_delegate_rule_set(&mut context, payer).await; let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create_via_builder( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, None, None, true, TokenStandard::ProgrammableNonFungible, None, Some(rule_set), Some(0), Some(PrintSupply::Zero), ) .await .unwrap(); test_metadata .mint_via_builder(&mut context, 1, Some(auth_data)) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, default_scopes(), ) .await .unwrap(); let (sell_acc, sell_tx) = auctioneer_sell_pnft( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, &rule_set, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, 100_000_000, None, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let mut accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (destination_tr, _) = find_token_record_account(&test_metadata.mint.pubkey(), &buyer_token_account); let remaining_accounts = mtly_auction_house::accounts::ExecuteSaleRemainingAccounts { metadata_program: mpl_token_metadata::id(), edition: test_metadata.master_edition, owner_tr: test_metadata.token_record, destination_tr, auth_rules_program: mpl_token_auth_rules::id(), auth_rules: rule_set, sysvar_instructions: sysvar::instructions::id(), }; accounts.append(&mut remaining_accounts.to_account_metas(None)); //@TODO: remove later if let Some(md_ix) = accounts .iter() .position(|x| x.pubkey == test_metadata.pubkey) { accounts[md_ix].is_writable = true; } let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, buyer_price_with_fees: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn auctioneer_execute_sale_pnft_price_with_fees_no_creators_success() { let sfbp = 500; auctioneer_execute_sale_pnft_price_with_fees_with_creators_and_sfbp(None, sfbp).await; } #[tokio::test] async fn auctioneer_execute_sale_pnft_price_with_fees_zero_sfbp_success() { let sfbp = 0; let metadata_creators = vec![ (Pubkey::new_unique(), 25, false), (Pubkey::new_unique(), 75, true), ]; auctioneer_execute_sale_pnft_price_with_fees_with_creators_and_sfbp( Some(metadata_creators), sfbp, ) .await; } #[tokio::test] async fn auctioneer_execute_sale_pnft_price_with_fees_success() { let sfbp = 500; let metadata_creators = vec![ (Pubkey::new_unique(), 25, false), (Pubkey::new_unique(), 75, true), ]; auctioneer_execute_sale_pnft_price_with_fees_with_creators_and_sfbp( Some(metadata_creators), sfbp, ) .await; } async fn auctioneer_execute_sale_pnft_price_with_fees_with_creators_and_sfbp( metadata_creators: Option>, sfbp: u16, ) { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let payer = context.payer.dirty_clone(); let (rule_set, auth_data) = create_sale_delegate_rule_set(&mut context, payer).await; let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10 * ONE_SOL) .await .unwrap(); if let Some(ref mdc) = metadata_creators { for (pubkey, _, fund_creator) in mdc { if *fund_creator { airdrop(&mut context, pubkey, ONE_SOL).await.unwrap(); } } } test_metadata .create_via_builder( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), metadata_creators.as_ref().map(|mdc| { mdc.clone() .iter() .map(|(address, share, _)| Creator { address: *address, verified: false, share: *share, }) .collect() }), sfbp, false, None, None, true, TokenStandard::ProgrammableNonFungible, None, Some(rule_set), Some(0), Some(PrintSupply::Zero), ) .await .unwrap(); test_metadata .mint_via_builder(&mut context, 1, Some(auth_data)) .await .unwrap(); let metadata = test_metadata.get_data(&mut context).await; let price = ONE_SOL; let price_with_fees = price + ((ah.seller_fee_basis_points as u64 * price) / 10000) + ((metadata.data.seller_fee_basis_points as u64 * price) / 10000); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, default_scopes(), ) .await .unwrap(); let (sell_acc, sell_tx) = auctioneer_sell_pnft( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, &rule_set, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10 * ONE_SOL) .await .unwrap(); airdrop(&mut context, &ah.auction_house_treasury, 10 * ONE_SOL) .await .unwrap(); let treasury_before = context .banks_client .get_account(ah.auction_house_treasury) .await .unwrap() .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, price, Some(price_with_fees), ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let mut accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); if let Some(ref mdc) = metadata_creators { for (pubkey, _, _) in mdc { accounts.push(AccountMeta { pubkey: *pubkey, is_signer: false, is_writable: true, }); } } let (destination_tr, _) = find_token_record_account(&test_metadata.mint.pubkey(), &buyer_token_account); let remaining_accounts = mtly_auction_house::accounts::ExecuteSaleRemainingAccounts { metadata_program: mpl_token_metadata::id(), edition: test_metadata.master_edition, owner_tr: test_metadata.token_record, destination_tr, auth_rules_program: mpl_token_auth_rules::id(), auth_rules: rule_set, sysvar_instructions: sysvar::instructions::id(), }; accounts.append(&mut remaining_accounts.to_account_metas(None)); //@TODO: remove later if let Some(md_ix) = accounts .iter() .position(|x| x.pubkey == test_metadata.pubkey) { accounts[md_ix].is_writable = true; } let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: price, buyer_price_with_fees: Some(price_with_fees), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10 * ONE_SOL) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); let mut metadata_creators_before: Vec> = Vec::new(); if let Some(ref mdc) = metadata_creators { for (creator, _, _) in mdc { metadata_creators_before .push(context.banks_client.get_account(*creator).await.unwrap()); } } context.banks_client.process_transaction(tx).await.unwrap(); let treasury_after = context .banks_client .get_account(ah.auction_house_treasury) .await .unwrap() .unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let mut metadata_creators_after: Vec> = Vec::new(); if let Some(ref mdc) = metadata_creators { for (creator, _, _) in mdc { metadata_creators_after.push(context.banks_client.get_account(*creator).await.unwrap()); } } for (creator_before, creator_after) in metadata_creators_before .iter() .zip(metadata_creators_after.iter()) { let before_lamps = match creator_before { Some(c) => c.lamports, None => 0, }; let after_lamps = match creator_after { Some(c) => c.lamports, None => 0, }; if sfbp == 0 { assert_eq!(before_lamps, after_lamps); } else { assert!(before_lamps < after_lamps); } } assert_eq!( treasury_before.lamports + ((ah.seller_fee_basis_points as u64 * price) / 10000), treasury_after.lamports ); assert_eq!(seller_before.lamports + price, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn execute_sale_pnft_existing_token_account_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let payer = context.payer.dirty_clone(); let (rule_set, auth_data) = create_sale_delegate_rule_set(&mut context, payer).await; let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create_via_builder( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, None, None, true, TokenStandard::ProgrammableNonFungible, None, Some(rule_set), Some(0), Some(PrintSupply::Zero), ) .await .unwrap(); test_metadata .mint_via_builder(&mut context, 1, Some(auth_data)) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell_pnft( &mut context, &ahkey, &ah, &test_metadata, &rule_set, 100_000_000, 1, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let mut accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (destination_tr, _) = find_token_record_account(&test_metadata.mint.pubkey(), &buyer_token_account); let remaining_accounts = mtly_auction_house::accounts::ExecuteSaleRemainingAccounts { metadata_program: mpl_token_metadata::id(), edition: test_metadata.master_edition, owner_tr: test_metadata.token_record, destination_tr, auth_rules_program: mpl_token_auth_rules::id(), auth_rules: rule_set, sysvar_instructions: sysvar::instructions::id(), }; accounts.append(&mut remaining_accounts.to_account_metas(None)); //@TODO: remove later if let Some(md_ix) = accounts .iter() .position(|x| x.pubkey == test_metadata.pubkey) { accounts[md_ix].is_writable = true; } let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn execute_sale_wrong_token_account_owner_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let malicious = Keypair::new(); airdrop(&mut context, &malicious.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let malicious_buyer_token_account = get_associated_token_address(&malicious.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &malicious, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: malicious_buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let err = context .banks_client .process_transaction(tx) .await .unwrap_err(); println!("{:?}", err); match err { BanksClientError::TransactionError(TransactionError::InstructionError( 0, InstructionError::Custom(6000), )) => (), _ => panic!("Expected custom error"), } } #[tokio::test] async fn execute_sale_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let _seller_ts_after = context .banks_client .get_account(sell_acc.seller_trade_state) .await .unwrap() .is_none(); let _buyer_ts_after = context .banks_client .get_account(bid_acc.buyer_trade_state) .await .unwrap() .is_none(); let _free_ts_after = context .banks_client .get_account(sell_acc.free_seller_trade_state) .await .unwrap() .is_none(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn execute_sale_bad_trade_state_failure() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer0 = Keypair::new(); airdrop(&mut context, &buyer0.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc0, _), buy_tx0) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer0, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx0) .await .unwrap(); let buyer0_token_account = get_associated_token_address(&buyer0.pubkey(), &test_metadata.mint.pubkey()); let buyer1 = Keypair::new(); airdrop(&mut context, &buyer1.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc1, _), buy_tx1) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer1, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx1) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer0.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: bid_acc1.buyer_trade_state, buyer_trade_state: bid_acc0.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer0_token_account, escrow_payment_account: bid_acc0.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer0.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let result = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(result, INVALID_SEEDS); } #[tokio::test] async fn auctioneer_execute_sale_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, default_scopes(), ) .await .unwrap(); let (sell_acc, sell_tx) = auctioneer_sell( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, 100_000_000, None, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, buyer_price_with_fees: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn auctioneer_execute_sale_bad_trade_state_failure() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, default_scopes(), ) .await .unwrap(); let (sell_acc, sell_tx) = auctioneer_sell( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer0 = Keypair::new(); airdrop(&mut context, &buyer0.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc0, buy_tx0) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer0, &auctioneer_authority, 100_000_000, None, ); context .banks_client .process_transaction(buy_tx0) .await .unwrap(); let buyer0_token_account = get_associated_token_address(&buyer0.pubkey(), &test_metadata.mint.pubkey()); let buyer1 = Keypair::new(); airdrop(&mut context, &buyer1.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc1, buy_tx1) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer1, &auctioneer_authority, 100_000_000, None, ); context .banks_client .process_transaction(buy_tx1) .await .unwrap(); let accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer0.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: bid_acc1.buyer_trade_state, buyer_trade_state: bid_acc0.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer0_token_account, escrow_payment_account: bid_acc0.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer0.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, buyer_price_with_fees: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let result = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(result, INVALID_SEEDS); } #[tokio::test] async fn auctioneer_execute_sale_missing_scope_fails() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); let scopes = vec![AuthorityScope::Sell, AuthorityScope::Buy]; delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, scopes.clone(), ) .await .unwrap(); let (sell_acc, sell_tx) = auctioneer_sell( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, 100_000_000, None, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, buyer_price_with_fees: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let error = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(error, MISSING_AUCTIONEER_SCOPE); } #[tokio::test] pub async fn auctioneer_execute_sale_no_delegate_fails() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let accounts = mtly_auction_house::accounts::AuctioneerExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, auctioneer_authority: auctioneer_authority.pubkey(), seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, ah_auctioneer_pda: auctioneer_pda, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::AuctioneerExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, buyer_price_with_fees: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&ah_auth.pubkey()), &[&ah_auth, &auctioneer_authority], context.last_blockhash, ); let error = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(error, ACCOUNT_NOT_INITIALIZED); } #[tokio::test] async fn execute_public_sale_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let price = 100_000_000; let fee_minus: u64 = price - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); // Create Listing let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, price, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); // Create Long Lasting Bid let public_bidder = Keypair::new(); airdrop(&mut context, &public_bidder.pubkey(), 10_000_000_000) .await .unwrap(); let ((public_bid_acc, _print_bid_receipt_acc), public_bid_tx) = public_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &public_bidder, price, ); context .banks_client .process_transaction(public_bid_tx) .await .unwrap(); // Create first regular private bid let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, price, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let ((_es_acc, _purchase_receipt_acc), first_sale_tx) = execute_sale( &mut context, &ahkey, &ah, &authority, &test_metadata, &buyer.pubkey(), &test_metadata.token.pubkey(), &sell_acc.token_account, &sell_acc.seller_trade_state, &bid_acc.buyer_trade_state, 1, price, ); airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context .banks_client .process_transaction(first_sale_tx) .await .unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); let new_seller = buyer; let public_bidder_token_account = get_associated_token_address(&public_bidder.pubkey(), &test_metadata.mint.pubkey()); let new_seller_before = context .banks_client .get_account(new_seller.pubkey()) .await .unwrap() .unwrap(); let public_bidder_token_before = &context .banks_client .get_account(public_bidder_token_account) .await .unwrap(); assert!(public_bidder_token_before.is_none()); let ((second_sell_acc, _), second_sell_tx) = sell_mint( &mut context, &ahkey, &ah, &test_metadata.mint.pubkey(), &new_seller, price, ); context .banks_client .process_transaction(second_sell_tx) .await .unwrap(); let ((public_sale_acc, purchase_receipt_acc), public_sale_tx) = execute_sale( &mut context, &ahkey, &ah, &authority, &test_metadata, &public_bidder.pubkey(), &new_seller.pubkey(), &second_sell_acc.token_account, &second_sell_acc.seller_trade_state, &public_bid_acc.buyer_trade_state, 1, price.to_owned(), ); context .banks_client .process_transaction(public_sale_tx) .await .unwrap(); let new_seller_after = context .banks_client .get_account(new_seller.pubkey()) .await .unwrap() .unwrap(); let public_bidder_token_after = Account::unpack_from_slice( context .banks_client .get_account(public_bidder_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); assert!(new_seller_before.lamports < new_seller_after.lamports); assert_eq!(public_bidder_token_after.amount, 1); let timestamp = context .banks_client .get_sysvar::() .await .unwrap() .unix_timestamp; let purchase_receipt_account = context .banks_client .get_account(purchase_receipt_acc.purchase_receipt) .await .expect("no purchase receipt") .expect("purchase receipt empty"); let purchase_receipt = PurchaseReceipt::try_deserialize(&mut purchase_receipt_account.data.as_ref()).unwrap(); assert_eq!(purchase_receipt.buyer, public_bidder.pubkey()); assert_eq!(purchase_receipt.seller, new_seller.pubkey()); assert_eq!(purchase_receipt.bookkeeper, authority.pubkey()); assert_eq!(purchase_receipt.price, price); assert_eq!(purchase_receipt.created_at, timestamp); assert_eq!(purchase_receipt.metadata, public_sale_acc.metadata); assert_eq!( purchase_receipt.auction_house, public_sale_acc.auction_house ); let bid_receipt_account = context .banks_client .get_account(purchase_receipt_acc.bid_receipt) .await .expect("no bid receipt") .expect("bid receipt empty"); let bid_receipt = BidReceipt::try_deserialize(&mut bid_receipt_account.data.as_ref()).unwrap(); assert_eq!( bid_receipt.purchase_receipt, Some(purchase_receipt_acc.purchase_receipt) ); assert_eq!(bid_receipt.canceled_at, None); assert_eq!(bid_receipt.token_account, None); let listing_receipt_account = context .banks_client .get_account(purchase_receipt_acc.listing_receipt) .await .expect("no listing receipt") .expect("listing receipt empty"); let listing_receipt = ListingReceipt::try_deserialize(&mut listing_receipt_account.data.as_ref()).unwrap(); assert_eq!( listing_receipt.purchase_receipt, Some(purchase_receipt_acc.purchase_receipt) ); assert_eq!(listing_receipt.canceled_at, None); } #[tokio::test] async fn auctioneer_execute_public_sale_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL * 2) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, default_scopes(), ) .await .unwrap(); let price = 100_000_000; let fee_minus: u64 = price - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); // Create Listing let (sell_acc, sell_tx) = auctioneer_sell( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); // Create Long Lasting Bid let public_bidder = Keypair::new(); airdrop(&mut context, &public_bidder.pubkey(), 10_000_000_000) .await .unwrap(); let (public_bid_acc, public_bid_tx) = auctioneer_public_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &public_bidder, &auctioneer_authority, price, None, ); context .banks_client .process_transaction(public_bid_tx) .await .unwrap(); // Create first regular private bid let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, price, None, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let (_es_acc, first_sale_tx) = auctioneer_execute_sale( &mut context, &ahkey, &ah, &ah_auth, &auctioneer_authority, &test_metadata, &buyer.pubkey(), &test_metadata.token.pubkey(), &sell_acc.token_account, &sell_acc.seller_trade_state, &bid_acc.buyer_trade_state, 1, price, None, ); airdrop(&mut context, &ah.auction_house_fee_account, ONE_SOL * 10) .await .unwrap(); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context .banks_client .process_transaction(first_sale_tx) .await .unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); let new_seller = buyer; let public_bidder_token_account = get_associated_token_address(&public_bidder.pubkey(), &test_metadata.mint.pubkey()); let new_seller_before = context .banks_client .get_account(new_seller.pubkey()) .await .unwrap() .unwrap(); let public_bidder_token_before = &context .banks_client .get_account(public_bidder_token_account) .await .unwrap(); assert!(public_bidder_token_before.is_none()); let (second_sell_acc, second_sell_tx) = auctioneer_sell_mint( &mut context, &ahkey, &ah, &test_metadata.mint.pubkey(), &new_seller, &auctioneer_authority, ); context .banks_client .process_transaction(second_sell_tx) .await .unwrap(); let (_public_sale_acc, public_sale_tx) = auctioneer_execute_sale( &mut context, &ahkey, &ah, &ah_auth, &auctioneer_authority, &test_metadata, &public_bidder.pubkey(), &new_seller.pubkey(), &second_sell_acc.token_account, &second_sell_acc.seller_trade_state, &public_bid_acc.buyer_trade_state, 1, price.to_owned(), None, ); context .banks_client .process_transaction(public_sale_tx) .await .unwrap(); let new_seller_after = context .banks_client .get_account(new_seller.pubkey()) .await .unwrap() .unwrap(); let public_bidder_token_after = Account::unpack_from_slice( context .banks_client .get_account(public_bidder_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); assert!(new_seller_before.lamports < new_seller_after.lamports); assert_eq!(public_bidder_token_after.amount, 1); } #[tokio::test] async fn auctioneer_execute_public_sale_missing_scope_fails() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let (auctioneer_pda, _) = find_auctioneer_pda(&ahkey, &auctioneer_authority.pubkey()); let scopes = vec![AuthorityScope::Sell, AuthorityScope::Buy]; delegate_auctioneer( &mut context, ahkey, &ah_auth, auctioneer_authority.pubkey(), auctioneer_pda, scopes.clone(), ) .await .unwrap(); let price = 100_000_000; // Create Listing let (sell_acc, sell_tx) = auctioneer_sell( &mut context, &ahkey, &ah, &test_metadata, &auctioneer_authority, ); context .banks_client .process_transaction(sell_tx) .await .unwrap(); // Create Long Lasting Bid let public_bidder = Keypair::new(); airdrop(&mut context, &public_bidder.pubkey(), 10_000_000_000) .await .unwrap(); let (_, public_bid_tx) = auctioneer_public_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &public_bidder, &auctioneer_authority, price, None, ); context .banks_client .process_transaction(public_bid_tx) .await .unwrap(); // Create first regular private bid let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let (bid_acc, buy_tx) = auctioneer_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, &auctioneer_authority, price, None, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let (_es_acc, first_sale_tx) = auctioneer_execute_sale( &mut context, &ahkey, &ah, &ah_auth, &auctioneer_authority, &test_metadata, &buyer.pubkey(), &test_metadata.token.pubkey(), &sell_acc.token_account, &sell_acc.seller_trade_state, &bid_acc.buyer_trade_state, 1, price, None, ); airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); let error = context .banks_client .process_transaction(first_sale_tx) .await .unwrap_err(); assert_error!(error, MISSING_AUCTIONEER_SCOPE); } #[tokio::test] async fn auctioneer_execute_public_sale_no_delegate_fails() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); // Delegate external auctioneer authority. let auctioneer_authority = Keypair::new(); airdrop(&mut context, &auctioneer_authority.pubkey(), ONE_SOL) .await .unwrap(); let price = 100_000_000; // Create Listing let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, price, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); // Create Long Lasting Bid let public_bidder = Keypair::new(); airdrop(&mut context, &public_bidder.pubkey(), 10_000_000_000) .await .unwrap(); let ((_acc, _print_bid_receipt_acc), public_bid_tx) = public_buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &public_bidder, price, ); context .banks_client .process_transaction(public_bid_tx) .await .unwrap(); // Create first regular private bid let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, price, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let (_es_acc, first_sale_tx) = auctioneer_execute_sale( &mut context, &ahkey, &ah, &ah_auth, &auctioneer_authority, &test_metadata, &buyer.pubkey(), &test_metadata.token.pubkey(), &sell_acc.token_account, &sell_acc.seller_trade_state, &bid_acc.buyer_trade_state, 1, price, None, ); let error = context .banks_client .process_transaction(first_sale_tx) .await .unwrap_err(); assert_error!(error, ACCOUNT_NOT_INITIALIZED); } #[tokio::test] async fn execute_sale_partial_order_success() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 6, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 600_000_000, 6); context .banks_client .process_transaction_with_commitment(sell_tx, CommitmentLevel::Confirmed) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 300_000_000, 3, ); context .banks_client .process_transaction_with_commitment(buy_tx, CommitmentLevel::Confirmed) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 6, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(300_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); context .banks_client .process_transaction_with_commitment(tx, CommitmentLevel::Confirmed) .await .unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let seller_token_after = Account::unpack_from_slice( context .banks_client .get_account(sell_acc.token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 300_000_000 - ((ah.seller_fee_basis_points as u64 * 300_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 3); assert_eq!(seller_token_after.amount, 3); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 300_000_000, 3, ); context .banks_client .process_transaction_with_commitment(buy_tx, CommitmentLevel::Confirmed) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(300_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); unwrap_ignoring_io_error_in_ci( context .banks_client .process_transaction_with_commitment(tx, CommitmentLevel::Confirmed) .await, ); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let seller_token_after = Account::unpack_from_slice( context .banks_client .get_account(sell_acc.token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 300_000_000 - ((ah.seller_fee_basis_points as u64 * 300_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 3); assert_eq!(seller_token_after.amount, 0); } #[tokio::test] async fn execute_sale_partial_order_bad_trade_state_failure() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 6, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 600_000_000, 6); context .banks_client .process_transaction_with_commitment(sell_tx, CommitmentLevel::Confirmed) .await .unwrap(); let buyer0 = Keypair::new(); airdrop(&mut context, &buyer0.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc0, _), buy_tx0) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer0, 300_000_000, 3, ); context .banks_client .process_transaction_with_commitment(buy_tx0, CommitmentLevel::Confirmed) .await .unwrap(); let buyer0_token_account = get_associated_token_address(&buyer0.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer0, &test_metadata.mint.pubkey()) .await .unwrap(); let buyer1 = Keypair::new(); airdrop(&mut context, &buyer1.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc1, _), buy_tx1) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer1, 300_000_000, 3, ); context .banks_client .process_transaction_with_commitment(buy_tx1, CommitmentLevel::Confirmed) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer0.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: partial_order_acc1.buyer_trade_state, buyer_trade_state: partial_order_acc0.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer0_token_account, escrow_payment_account: partial_order_acc0.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 6, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer0.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(300_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let result = context .banks_client .process_transaction_with_commitment(tx, CommitmentLevel::Confirmed) .await .unwrap_err(); assert_error!(result, INVALID_SEEDS); } #[tokio::test] async fn execute_sale_fail_buyer_trade_state_does_not_exist() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); transfer( &mut context, &test_metadata.mint.pubkey(), &test_metadata.token, &buyer, ) .await .unwrap(); let seller_token_account = get_associated_token_address(&test_metadata.token.pubkey(), &test_metadata.mint.pubkey()); let seller_token_after = Account::unpack_from_slice( context .banks_client .get_account(seller_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); assert_eq!(seller_token_after.amount, 0); assert_eq!(buyer_token_after.amount, 1); let ((bid_acc, _), _) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let error = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(error, BUYER_TRADE_STATE_NOT_VALID); } #[tokio::test] async fn execute_sale_partial_order_order_exceeds_tokens() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 6, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 600_000_000, 6); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 300_000_000, 3, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 6, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(300_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let seller_token_after = Account::unpack_from_slice( context .banks_client .get_account(sell_acc.token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 300_000_000 - ((ah.seller_fee_basis_points as u64 * 300_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 3); assert_eq!(seller_token_after.amount, 3); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 400_000_000, 4, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(4), partial_order_price: Some(400_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let error = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error_ignoring_io_error_in_ci(&error, NOT_ENOUGH_TOKENS_AVAIL_FOR_PURCHASE); } #[tokio::test] async fn execute_sale_partial_order_fail_price_mismatch() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 6, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 600_000_000, 6); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 300_000_000, 3, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 6, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(300_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(!buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let seller_token_after = Account::unpack_from_slice( context .banks_client .get_account(sell_acc.token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 300_000_000 - ((ah.seller_fee_basis_points as u64 * 300_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 3); assert_eq!(seller_token_after.amount, 3); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 400_000_000, 3, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: Some(400_000_000), } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let error: BanksClientError = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error_ignoring_io_error_in_ci(&error, PARTIAL_BUY_PRICE_MISMATCH); } #[tokio::test] async fn execute_sale_partial_order_fail_missing_elements() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 6, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 600_000_000, 6); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((partial_order_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 300_000_000, 3, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); create_associated_token_account(&mut context, &buyer, &test_metadata.mint.pubkey()) .await .unwrap(); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: partial_order_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: partial_order_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 6, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecutePartialSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 6, buyer_price: 600_000_000, partial_order_size: Some(3), partial_order_price: None, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let error = context .banks_client .process_transaction(tx) .await .unwrap_err(); assert_error!(error, MISSING_ELEMENTS_NEEDED_FOR_PARTIAL_BUY); } #[tokio::test] async fn execute_sale_pre_partial_bid() { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), None, 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let data: Vec = mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: data[0..data.len()].to_owned(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let fee_minus: u64 = 100_000_000 - ((ah.seller_fee_basis_points as u64 * 100_000_000) / 10000); assert_eq!(seller_before.lamports + fee_minus, seller_after.lamports); assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); } #[tokio::test] async fn execute_sale_creator_zero_balance() { execute_sale_with_creators(vec![(Pubkey::new_unique(), 100, false)]).await; } #[tokio::test] async fn execute_sale_creator_mixed_funded() { execute_sale_with_creators(vec![ (Pubkey::new_unique(), 25, false), (Pubkey::new_unique(), 75, true), ]) .await; } async fn execute_sale_with_creators(metadata_creators: Vec<(Pubkey, u8, bool)>) { let mut context = auction_house_program_test().start_with_context().await; // Payer Wallet let (ah, ahkey, authority) = existing_auction_house_test_context(&mut context) .await .unwrap(); let test_metadata = Metadata::new(); airdrop(&mut context, &test_metadata.token.pubkey(), 10_000_000_000) .await .unwrap(); for (pubkey, _, fund_creator) in &metadata_creators { if *fund_creator { airdrop(&mut context, pubkey, 100_000_000).await.unwrap(); } } test_metadata .create( &mut context, "Test".to_string(), "TST".to_string(), "uri".to_string(), Some( metadata_creators .clone() .iter() .map(|(address, share, _)| Creator { address: *address, verified: false, share: *share, }) .collect(), ), 10, false, 1, ) .await .unwrap(); let ((sell_acc, _), sell_tx) = sell(&mut context, &ahkey, &ah, &test_metadata, 100_000_000, 1); context .banks_client .process_transaction(sell_tx) .await .unwrap(); let buyer = Keypair::new(); airdrop(&mut context, &buyer.pubkey(), 10_000_000_000) .await .unwrap(); let ((bid_acc, _), buy_tx) = buy( &mut context, &ahkey, &ah, &test_metadata, &test_metadata.token.pubkey(), &buyer, 100_000_000, 1, ); context .banks_client .process_transaction(buy_tx) .await .unwrap(); let buyer_token_account = get_associated_token_address(&buyer.pubkey(), &test_metadata.mint.pubkey()); let mut accounts = mtly_auction_house::accounts::ExecuteSale { buyer: buyer.pubkey(), seller: test_metadata.token.pubkey(), auction_house: ahkey, metadata: test_metadata.pubkey, token_account: sell_acc.token_account, authority: ah.authority, seller_trade_state: sell_acc.seller_trade_state, buyer_trade_state: bid_acc.buyer_trade_state, token_program: spl_token::id(), free_trade_state: sell_acc.free_seller_trade_state, seller_payment_receipt_account: test_metadata.token.pubkey(), buyer_receipt_token_account: buyer_token_account, escrow_payment_account: bid_acc.escrow_payment_account, token_mint: test_metadata.mint.pubkey(), auction_house_fee_account: ah.auction_house_fee_account, auction_house_treasury: ah.auction_house_treasury, treasury_mint: ah.treasury_mint, program_as_signer: sell_acc.program_as_signer, system_program: system_program::id(), ata_program: spl_associated_token_account::id(), rent: sysvar::rent::id(), } .to_account_metas(None); for (pubkey, _, _) in &metadata_creators { accounts.push(AccountMeta { pubkey: *pubkey, is_signer: false, is_writable: true, }); } let (_, free_sts_bump) = find_trade_state_address( &test_metadata.token.pubkey(), &ahkey, &sell_acc.token_account, &ah.treasury_mint, &test_metadata.mint.pubkey(), 0, 1, ); let (_, escrow_bump) = find_escrow_payment_address(&ahkey, &buyer.pubkey()); let (_, pas_bump) = find_program_as_signer_address(); let instruction = Instruction { program_id: mtly_auction_house::id(), data: mtly_auction_house::instruction::ExecuteSale { escrow_payment_bump: escrow_bump, _free_trade_state_bump: free_sts_bump, program_as_signer_bump: pas_bump, token_size: 1, buyer_price: 100_000_000, } .data(), accounts, }; airdrop(&mut context, &ah.auction_house_fee_account, 10_000_000_000) .await .unwrap(); let tx = Transaction::new_signed_with_payer( &[instruction], Some(&authority.pubkey()), &[&authority], context.last_blockhash, ); let seller_before = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let mut metadata_creators_before: Vec> = Vec::new(); for (creator, _, _) in &metadata_creators { metadata_creators_before.push(context.banks_client.get_account(*creator).await.unwrap()); } let buyer_token_before = &context .banks_client .get_account(buyer_token_account) .await .unwrap(); assert!(buyer_token_before.is_none()); context.banks_client.process_transaction(tx).await.unwrap(); let seller_after = context .banks_client .get_account(test_metadata.token.pubkey()) .await .unwrap() .unwrap(); let mut metadata_creators_after: Vec> = Vec::new(); for (creator, _, _) in &metadata_creators { metadata_creators_after.push(context.banks_client.get_account(*creator).await.unwrap()); } let buyer_token_after = Account::unpack_from_slice( context .banks_client .get_account(buyer_token_account) .await .unwrap() .unwrap() .data .as_slice(), ) .unwrap(); let _seller_ts_after = context .banks_client .get_account(sell_acc.seller_trade_state) .await .unwrap() .is_none(); let _buyer_ts_after = context .banks_client .get_account(bid_acc.buyer_trade_state) .await .unwrap() .is_none(); let _free_ts_after = context .banks_client .get_account(sell_acc.free_seller_trade_state) .await .unwrap() .is_none(); for (creator_before, creator_after) in metadata_creators_before .iter() .zip(metadata_creators_after.iter()) { if creator_before.is_none() { assert_eq!(creator_before.is_none(), creator_after.is_none()); } else { assert!( creator_before.as_ref().unwrap().lamports < creator_after.as_ref().unwrap().lamports ); } } assert!(seller_before.lamports < seller_after.lamports); assert_eq!(buyer_token_after.amount, 1); }