use crate::{
errors::AuctionError,
processor::{AuctionData, AuctionDataExtended, BidderMetadata, BidderPot},
utils::{
assert_derivation, assert_initialized, assert_owned_by, assert_signer,
assert_token_program_matches_package, close_token_account, create_or_allocate_account_raw,
spl_token_transfer, TokenTransferParams,
},
BIDDER_POT_TOKEN, EXTENDED, PREFIX,
};
use super::AuctionState;
use {
borsh::{BorshDeserialize, BorshSerialize},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program::invoke_signed,
program_error::ProgramError,
program_pack::Pack,
pubkey::Pubkey,
system_instruction,
sysvar::{clock::Clock, Sysvar},
},
spl_token::state::Account,
};
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct CancelBidArgs {
pub resource: Pubkey,
}
struct Accounts<'a, 'b: 'a> {
auction: &'a AccountInfo<'b>,
auction_extended: &'a AccountInfo<'b>,
bidder_meta: &'a AccountInfo<'b>,
bidder_pot: &'a AccountInfo<'b>,
bidder_pot_token: &'a AccountInfo<'b>,
bidder: &'a AccountInfo<'b>,
bidder_token: &'a AccountInfo<'b>,
clock_sysvar: &'a AccountInfo<'b>,
mint: &'a AccountInfo<'b>,
rent: &'a AccountInfo<'b>,
system: &'a AccountInfo<'b>,
token_program: &'a AccountInfo<'b>,
}
fn parse_accounts<'a, 'b: 'a>(
program_id: &Pubkey,
accounts: &'a [AccountInfo<'b>],
) -> Result<Accounts<'a, 'b>, ProgramError> {
let account_iter = &mut accounts.iter();
let accounts = Accounts {
bidder: next_account_info(account_iter)?,
bidder_token: next_account_info(account_iter)?,
bidder_pot: next_account_info(account_iter)?,
bidder_pot_token: next_account_info(account_iter)?,
bidder_meta: next_account_info(account_iter)?,
auction: next_account_info(account_iter)?,
auction_extended: next_account_info(account_iter)?,
mint: next_account_info(account_iter)?,
clock_sysvar: next_account_info(account_iter)?,
rent: next_account_info(account_iter)?,
system: next_account_info(account_iter)?,
token_program: next_account_info(account_iter)?,
};
assert_owned_by(accounts.auction, program_id)?;
assert_owned_by(accounts.auction_extended, program_id)?;
assert_owned_by(accounts.bidder_meta, program_id)?;
assert_owned_by(accounts.mint, &spl_token::id())?;
assert_owned_by(accounts.bidder_pot, program_id)?;
assert_owned_by(accounts.bidder_pot_token, &spl_token::id())?;
assert_signer(accounts.bidder)?;
assert_token_program_matches_package(accounts.token_program)?;
if *accounts.token_program.key != spl_token::id() {
return Err(AuctionError::InvalidTokenProgram.into());
}
Ok(accounts)
}
pub fn cancel_bid(
program_id: &Pubkey,
accounts: &[AccountInfo],
args: CancelBidArgs,
) -> ProgramResult {
msg!("+ Processing Cancelbid");
let accounts = parse_accounts(program_id, accounts)?;
let actual_account: Account = assert_initialized(accounts.bidder_pot_token)?;
if actual_account.owner != *accounts.auction.key {
return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into());
}
let auction_bump = assert_derivation(
program_id,
accounts.auction,
&[
PREFIX.as_bytes(),
program_id.as_ref(),
args.resource.as_ref(),
],
)?;
let auction_seeds = &[
PREFIX.as_bytes(),
program_id.as_ref(),
args.resource.as_ref(),
&[auction_bump],
];
let mut auction = AuctionData::from_account_info(accounts.auction)?;
if auction.token_mint != *accounts.mint.key {
return Err(AuctionError::IncorrectMint.into());
}
let mut auction_extended = AuctionDataExtended::from_account_info(accounts.auction_extended)?;
let clock = Clock::from_account_info(accounts.clock_sysvar)?;
let metadata_bump = assert_derivation(
program_id,
accounts.bidder_meta,
&[
PREFIX.as_bytes(),
program_id.as_ref(),
accounts.auction.key.as_ref(),
accounts.bidder.key.as_ref(),
"metadata".as_bytes(),
],
)?;
if accounts.bidder_meta.owner != program_id {
return Err(AuctionError::MetadataInvalid.into());
}
let pot_seeds = [
PREFIX.as_bytes(),
program_id.as_ref(),
accounts.auction.key.as_ref(),
accounts.bidder.key.as_ref(),
];
let pot_bump = assert_derivation(program_id, accounts.bidder_pot, &pot_seeds)?;
let bump_authority_seeds = &[
PREFIX.as_bytes(),
program_id.as_ref(),
accounts.auction.key.as_ref(),
accounts.bidder.key.as_ref(),
&[pot_bump],
];
if accounts.bidder_pot.data_is_empty() {
return Err(AuctionError::BidderPotDoesNotExist.into());
}
let winner_bid_index = auction.is_winner(accounts.bidder.key);
if auction.ended(clock.unix_timestamp)? && winner_bid_index.is_some() {
return Err(AuctionError::InvalidState.into());
}
if let Some(bid_index) = winner_bid_index {
if let Some(instant_sale_price) = auction_extended.instant_sale_price {
if auction.bid_state.amount(bid_index) >= instant_sale_price {
return Err(AuctionError::InvalidState.into());
}
}
}
let bidder_pot = BidderPot::from_account_info(accounts.bidder_pot)?;
if bidder_pot.bidder_pot != *accounts.bidder_pot_token.key {
return Err(AuctionError::BidderPotTokenAccountOwnerMismatch.into());
}
let account: Account = Account::unpack_from_slice(&accounts.bidder_pot_token.data.borrow())?;
spl_token_transfer(TokenTransferParams {
source: accounts.bidder_pot_token.clone(),
destination: accounts.bidder_token.clone(),
authority: accounts.auction.clone(),
authority_signer_seeds: auction_seeds,
token_program: accounts.token_program.clone(),
amount: account.amount,
})?;
let metadata = BidderMetadata::from_account_info(accounts.bidder_meta)?;
let already_cancelled = metadata.cancelled;
BidderMetadata {
cancelled: true,
..metadata
}
.serialize(&mut *accounts.bidder_meta.data.borrow_mut())?;
if auction.state != AuctionState::Ended {
assert_derivation(
program_id,
accounts.auction_extended,
&[
PREFIX.as_bytes(),
program_id.as_ref(),
args.resource.as_ref(),
EXTENDED.as_bytes(),
],
)?;
msg!("Already cancelled is {:?}", already_cancelled);
if !already_cancelled && auction_extended.total_uncancelled_bids > 0 {
auction_extended.total_uncancelled_bids = auction_extended
.total_uncancelled_bids
.checked_sub(1)
.ok_or(AuctionError::NumericalOverflowError)?;
}
auction_extended.serialize(&mut *accounts.auction_extended.data.borrow_mut())?;
auction.bid_state.cancel_bid(*accounts.bidder.key);
auction.serialize(&mut *accounts.auction.data.borrow_mut())?;
}
Ok(())
}