use solana_program::msg;
use crate::{
error::MetaplexError,
instruction::EmptyPaymentAccountArgs,
state::{
get_auction_manager, AuctionManager, Key, PayoutTicket, Store, MAX_PAYOUT_TICKET_SIZE,
PREFIX, TOTALS,
},
utils::{
assert_derivation, assert_initialized, assert_is_ata, assert_owned_by, assert_rent_exempt,
assert_safety_deposit_config_valid, create_or_allocate_account_raw, spl_token_transfer,
},
};
use borsh::BorshSerialize;
use mpl_auction::processor::AuctionData;
use mpl_token_metadata::state::{MasterEditionV1, Metadata, TokenMetadataAccount};
use mpl_token_vault::state::SafetyDepositBox;
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_error::ProgramError,
program_option::COption,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use spl_token::state::Account;
fn assert_destination_ownership_validity(
auction_manager: &Box<dyn AuctionManager>,
metadata: &Metadata,
destination_info: &AccountInfo,
destination: &Account,
_store: &Store,
creator_index: Option<u8>,
) -> ProgramResult {
if let Some(creators) = &metadata.data.creators {
if let Some(index) = creator_index {
if (index as usize) < creators.len() {
let creator = &creators[index as usize];
if destination.owner != creator.address {
return Err(MetaplexError::IncorrectOwner.into());
}
assert_is_ata(destination_info, &creator.address, &destination.mint)?;
} else {
return Err(MetaplexError::InvalidCreatorIndex.into());
}
} else if destination.owner != auction_manager.authority() {
return Err(MetaplexError::IncorrectOwner.into());
}
} else if destination.owner != auction_manager.authority() {
return Err(MetaplexError::IncorrectOwner.into());
}
if destination.delegate != COption::None {
return Err(MetaplexError::DelegateShouldBeNone.into());
}
if destination.close_authority != COption::None {
return Err(MetaplexError::CloseAuthorityShouldBeNone.into());
}
Ok(())
}
fn calculate_owed_amount(
auction_token_tracker_info: Option<&AccountInfo>,
safety_deposit_config_info: Option<&AccountInfo>,
auction_manager: &Box<dyn AuctionManager>,
auction: &AuctionData,
metadata: &Metadata,
winning_config_index: &Option<u8>,
winning_config_item_index: &Option<u8>,
creator_index: &Option<u8>,
) -> Result<u64, ProgramError> {
let primary_sale_happened = auction_manager.get_primary_sale_happened(
metadata,
*winning_config_index,
*winning_config_item_index,
)?;
let mut amount_available_to_split: u128 = match winning_config_index {
Some(index) => auction.bid_state.amount(*index as usize) as u128,
None => {
auction_manager.get_collected_to_accept_payment(safety_deposit_config_info)?
}
};
if winning_config_index.is_some() {
msg!("Winning config index {:?}", winning_config_index.unwrap());
}
if winning_config_item_index.is_some() {
msg!(
"Winning config item index {:?}",
winning_config_item_index.unwrap()
);
}
if creator_index.is_some() {
msg!("Creator index {:?}", creator_index.unwrap());
}
msg!("Amount available to split {:?}", amount_available_to_split);
let numerator: u128 = match creator_index {
Some(_) => {
if primary_sale_happened {
metadata.data.seller_fee_basis_points as u128
} else {
10000
}
}
None => {
if primary_sale_happened {
(10000 - metadata.data.seller_fee_basis_points) as u128
} else {
0u128
}
}
};
msg!("Numerator {:?}", numerator);
let artist_further_multiplier = match creator_index {
Some(index) => match &metadata.data.creators {
Some(creators) => (creators[*index as usize].share as u128) * 100u128,
None => return Err(MetaplexError::CreatorIndexExpected.into()),
},
None => 10000,
};
msg!("Artist further multiplier {:?}", artist_further_multiplier);
amount_available_to_split = amount_available_to_split
.checked_mul(numerator)
.ok_or(MetaplexError::NumericalOverflowError)?;
msg!(
"Amount available to split after numerator mult {:?}",
amount_available_to_split,
);
amount_available_to_split = amount_available_to_split
.checked_mul(artist_further_multiplier)
.ok_or(MetaplexError::NumericalOverflowError)?;
msg!(
"Amount available to split after artist further multiplier mult {:?}",
amount_available_to_split,
);
if amount_available_to_split == 0 {
return Ok(0u64);
}
let proportion_divisor = match winning_config_index {
Some(val) => auction_manager.get_number_of_unique_token_types_for_this_winner(
*val as usize,
auction_token_tracker_info,
)?,
None => 1,
};
let proportional_amount_available_to_split = amount_available_to_split
.checked_div(proportion_divisor)
.ok_or(MetaplexError::NumericalOverflowError)?;
msg!(
"Divided the amount by {:?} to get {:?} due to sharing reward with other prizes",
proportion_divisor,
proportional_amount_available_to_split
);
let final_amount_available_to_split = proportional_amount_available_to_split
.checked_div(10000 * 10000)
.ok_or(MetaplexError::NumericalOverflowError)?;
msg!("Final amount mult {:?}", final_amount_available_to_split);
Ok(final_amount_available_to_split as u64)
}
pub fn process_empty_payment_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
args: EmptyPaymentAccountArgs,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let accept_payment_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let auction_manager_info = next_account_info(account_info_iter)?;
let payout_ticket_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
let metadata_info = next_account_info(account_info_iter)?;
let master_edition_info = next_account_info(account_info_iter)?;
let safety_deposit_info = next_account_info(account_info_iter)?;
let store_info = next_account_info(account_info_iter)?;
let vault_info = next_account_info(account_info_iter)?;
let auction_info = next_account_info(account_info_iter)?;
let token_program_info = next_account_info(account_info_iter)?;
let system_info = next_account_info(account_info_iter)?;
let rent_info = next_account_info(account_info_iter)?;
let auction_token_tracker_info = next_account_info(account_info_iter).ok();
let safety_deposit_config_info = next_account_info(account_info_iter).ok();
if let Some(tracker_info) = auction_token_tracker_info {
assert_derivation(
program_id,
tracker_info,
&[
PREFIX.as_bytes(),
&program_id.as_ref(),
auction_manager_info.key.as_ref(),
TOTALS.as_bytes(),
],
)?;
}
let rent = &Rent::from_account_info(&rent_info)?;
let auction_manager = get_auction_manager(auction_manager_info)?;
let store = Store::from_account_info(store_info)?;
let safety_deposit = SafetyDepositBox::from_account_info(safety_deposit_info)?;
let metadata = Metadata::from_account_info(metadata_info)?;
let auction = AuctionData::from_account_info(auction_info)?;
let destination: Account = assert_initialized(destination_info)?;
let accept_payment: Account = assert_initialized(accept_payment_info)?;
if auction_manager.store() != *store_info.key {
return Err(MetaplexError::AuctionManagerStoreMismatch.into());
}
msg!(
"At this point, accept payment has {:?} in it",
accept_payment.amount
);
auction_manager.assert_all_bids_claimed(&auction)?;
if *token_program_info.key != store.token_program {
return Err(MetaplexError::AuctionManagerTokenProgramMismatch.into());
}
assert_owned_by(auction_manager_info, program_id)?;
if !payout_ticket_info.data_is_empty() {
assert_owned_by(payout_ticket_info, program_id)?;
}
assert_owned_by(destination_info, token_program_info.key)?;
assert_owned_by(accept_payment_info, token_program_info.key)?;
assert_owned_by(metadata_info, &store.token_metadata_program)?;
if *master_edition_info.key != solana_program::system_program::id() {
assert_owned_by(master_edition_info, &store.token_metadata_program)?;
}
assert_owned_by(safety_deposit_info, &store.token_vault_program)?;
assert_owned_by(store_info, program_id)?;
assert_owned_by(vault_info, &store.token_vault_program)?;
assert_owned_by(auction_info, &store.auction_program)?;
assert_rent_exempt(rent, destination_info)?;
auction_manager.assert_winning_config_safety_deposit_validity(
&safety_deposit,
args.winning_config_index,
args.winning_config_item_index,
)?;
assert_safety_deposit_config_valid(
program_id,
auction_manager_info,
safety_deposit_info,
safety_deposit_config_info,
&auction_manager.key(),
)?;
assert_destination_ownership_validity(
&auction_manager,
&metadata,
destination_info,
&destination,
&store,
args.creator_index,
)?;
if auction_manager.vault() != *vault_info.key {
return Err(MetaplexError::AuctionManagerVaultMismatch.into());
}
if auction_manager.auction() != *auction_info.key {
return Err(MetaplexError::AuctionManagerAuctionMismatch.into());
}
if safety_deposit.vault != *vault_info.key {
return Err(MetaplexError::SafetyDepositBoxVaultMismatch.into());
}
if metadata.mint != safety_deposit.token_mint {
if master_edition_info.data.borrow()[0]
== mpl_token_metadata::state::Key::MasterEditionV1 as u8
{
let master_edition: MasterEditionV1 =
MasterEditionV1::from_account_info(master_edition_info)?;
if master_edition.printing_mint != safety_deposit.token_mint
&& master_edition.one_time_printing_authorization_mint != safety_deposit.token_mint
{
return Err(MetaplexError::SafetyDepositBoxMetadataMismatch.into());
}
} else {
return Err(MetaplexError::SafetyDepositBoxMetadataMismatch.into());
}
}
if auction_manager.accept_payment() != *accept_payment_info.key {
return Err(MetaplexError::AcceptPaymentMismatch.into());
}
if destination.mint != accept_payment.mint {
return Err(MetaplexError::AcceptPaymentMintMismatch.into());
}
let winning_config_index_key: String = match args.winning_config_index {
Some(val) => val.to_string(),
None => "participation".to_owned(),
};
let winning_config_item_index_key: String = match args.winning_config_item_index {
Some(val) => val.to_string(),
None => "0".to_owned(),
};
let creator_index_key: String = match args.creator_index {
Some(val) => val.to_string(),
None => "auctioneer".to_owned(),
};
let payout_bump = assert_derivation(
program_id,
payout_ticket_info,
&[
PREFIX.as_bytes(),
auction_manager_info.key.as_ref(),
winning_config_index_key.as_bytes(),
winning_config_item_index_key.as_bytes(),
creator_index_key.as_bytes(),
&safety_deposit_info.key.as_ref(),
&destination.owner.as_ref(),
],
)?;
let payout_seeds = &[
PREFIX.as_bytes(),
auction_manager_info.key.as_ref(),
winning_config_index_key.as_bytes(),
winning_config_item_index_key.as_bytes(),
creator_index_key.as_bytes(),
&safety_deposit_info.key.as_ref(),
&destination.owner.as_ref(),
&[payout_bump],
];
if payout_ticket_info.data_is_empty() {
create_or_allocate_account_raw(
*program_id,
payout_ticket_info,
rent_info,
system_info,
payer_info,
MAX_PAYOUT_TICKET_SIZE,
payout_seeds,
)?;
}
let mut payout_ticket = PayoutTicket::from_account_info(payout_ticket_info)?;
payout_ticket.recipient = destination.owner;
payout_ticket.key = Key::PayoutTicketV1;
let amount = calculate_owed_amount(
auction_token_tracker_info,
safety_deposit_config_info,
&auction_manager,
&auction,
&metadata,
&args.winning_config_index,
&args.winning_config_item_index,
&args.creator_index,
)?;
let final_amount = amount
.checked_sub(payout_ticket.amount_paid)
.ok_or(MetaplexError::NumericalOverflowError)?;
if final_amount > 0 {
payout_ticket.amount_paid = payout_ticket
.amount_paid
.checked_add(final_amount)
.ok_or(MetaplexError::NumericalOverflowError)?;
let auction_key = auction_manager.auction();
let bump_seed = assert_derivation(
program_id,
auction_manager_info,
&[PREFIX.as_bytes(), auction_key.as_ref()],
)?;
let authority_seeds = &[PREFIX.as_bytes(), auction_key.as_ref(), &[bump_seed]];
spl_token_transfer(
accept_payment_info.clone(),
destination_info.clone(),
final_amount,
auction_manager_info.clone(),
authority_seeds,
token_program_info.clone(),
)?;
}
payout_ticket.serialize(&mut *payout_ticket_info.data.borrow_mut())?;
Ok(())
}