refactor: instant sale considered open edition auction
This commit is contained in:
parent
ff4ed1b18a
commit
e18c4e0bf5
|
@ -7,3 +7,6 @@ indent_size = 2
|
|||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
||||
|
|
|
@ -15,7 +15,7 @@ import { findProgramAddress } from '../utils';
|
|||
export const AUCTION_PREFIX = 'auction';
|
||||
export const METADATA = 'metadata';
|
||||
export const EXTENDED = 'extended';
|
||||
export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200;
|
||||
export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 9 + 191;
|
||||
|
||||
export enum AuctionState {
|
||||
Created = 0,
|
||||
|
|
|
@ -31,20 +31,11 @@ pub enum AuctionInstruction {
|
|||
/// Create a new auction account bound to a resource, initially in a pending state.
|
||||
/// 0. `[signer]` The account creating the auction, which is authorised to make changes.
|
||||
/// 1. `[writable]` Uninitialized auction account.
|
||||
/// 2. `[writable]` Auction extended data account.
|
||||
/// 2. `[writable]` Auction extended data account (pda relative to auction of ['auction', program id, vault key, 'extended']).
|
||||
/// 3. `[]` Rent sysvar
|
||||
/// 4. `[]` System account
|
||||
CreateAuction(CreateAuctionArgs),
|
||||
|
||||
/// Create a new auction account bound to a resource, initially in a pending state.
|
||||
/// The only one difference with above instruction it's instant_sale_price parameter in CreateAuctionArgsV2
|
||||
/// 0. `[signer]` The account creating the auction, which is authorised to make changes.
|
||||
/// 1. `[writable]` Uninitialized auction account.
|
||||
/// 2. `[writable]` Auction extended data account.
|
||||
/// 3. `[]` Rent sysvar
|
||||
/// 4. `[]` System account
|
||||
CreateAuctionV2(CreateAuctionArgsV2),
|
||||
|
||||
/// Move SPL tokens from winning bid to the destination account.
|
||||
/// 0. `[writable]` The destination account
|
||||
/// 1. `[writable]` The bidder pot token account
|
||||
|
@ -55,7 +46,7 @@ pub enum AuctionInstruction {
|
|||
/// 6. `[]` Token mint of the auction
|
||||
/// 7. `[]` Clock sysvar
|
||||
/// 8. `[]` Token program
|
||||
/// 9. `[]` Auction extended
|
||||
/// 9. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||
ClaimBid(ClaimBidArgs),
|
||||
|
||||
/// Ends an auction, regardless of end timing conditions
|
||||
|
@ -85,6 +76,15 @@ pub enum AuctionInstruction {
|
|||
/// 11. `[]` System program
|
||||
/// 12. `[]` SPL Token Program
|
||||
PlaceBid(PlaceBidArgs),
|
||||
|
||||
/// Create a new auction account bound to a resource, initially in a pending state.
|
||||
/// The only one difference with above instruction it's additional parameters in CreateAuctionArgsV2
|
||||
/// 0. `[signer]` The account creating the auction, which is authorised to make changes.
|
||||
/// 1. `[writable]` Uninitialized auction account.
|
||||
/// 2. `[writable]` Auction extended data account (pda relative to auction of ['auction', program id, vault key, 'extended']).
|
||||
/// 3. `[]` Rent sysvar
|
||||
/// 4. `[]` System account
|
||||
CreateAuctionV2(CreateAuctionArgsV2),
|
||||
}
|
||||
|
||||
/// Creates an CreateAuction instruction.
|
||||
|
|
|
@ -370,6 +370,16 @@ impl AuctionData {
|
|||
self.bid_state.winner_at(idx)
|
||||
}
|
||||
|
||||
pub fn consider_instant_bid(&mut self, instant_sale_price: Option<u64>) {
|
||||
// Check if all the lots were sold with instant_sale_price
|
||||
if let Some(price) = instant_sale_price {
|
||||
if self.bid_state.has_instant_bid(price) {
|
||||
msg!("All the lots were sold with instant_sale_price, auction is ended");
|
||||
self.state = AuctionState::Ended;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_bid(
|
||||
&mut self,
|
||||
bid: Bid,
|
||||
|
@ -394,6 +404,7 @@ impl AuctionData {
|
|||
PriceFloor::MinimumPrice(min) => min[0],
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
self.bid_state.place_bid(
|
||||
bid,
|
||||
tick_size,
|
||||
|
@ -401,7 +412,11 @@ impl AuctionData {
|
|||
minimum,
|
||||
instant_sale_price,
|
||||
&mut self.state,
|
||||
)
|
||||
)?;
|
||||
|
||||
self.consider_instant_bid(instant_sale_price);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -590,17 +605,6 @@ impl BidState {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if all the lots were sold with instant_sale_price
|
||||
if let Some(instant_sale_amount) = instant_sale_price {
|
||||
// bids.len() - max = index of the last winner bid
|
||||
if bids.len() >= *max
|
||||
&& bids[bids.len() - *max].1 >= instant_sale_amount
|
||||
{
|
||||
msg!("All the lots were sold with instant_sale_price, auction is ended");
|
||||
*auction_state = AuctionState::Ended;
|
||||
}
|
||||
}
|
||||
|
||||
let max_size = BidState::max_array_size_for(*max);
|
||||
|
||||
if bids.len() > max_size {
|
||||
|
@ -702,6 +706,17 @@ impl BidState {
|
|||
BidState::OpenEdition { bids, max } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_instant_bid(&self, instant_sale_amount: u64) -> bool {
|
||||
match self {
|
||||
// In a capped auction, track the limited number of winners.
|
||||
BidState::EnglishAuction { bids, max } | BidState::OpenEdition { bids, max } => {
|
||||
// bids.len() - max = index of the last winner bid
|
||||
bids.len() >= *max && bids[bids.len() - *max].1 >= instant_sale_amount
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
@ -132,36 +132,34 @@ pub fn claim_bid(
|
|||
return Err(AuctionError::InvalidState.into());
|
||||
}
|
||||
|
||||
let mut auction_extended: Option<AuctionDataExtended> = None;
|
||||
if let Some(auction_extended_info) = accounts.auction_extended {
|
||||
let instant_sale_price = accounts.auction_extended.and_then(|info| {
|
||||
assert_derivation(
|
||||
program_id,
|
||||
auction_extended_info,
|
||||
info,
|
||||
&[
|
||||
PREFIX.as_bytes(),
|
||||
program_id.as_ref(),
|
||||
args.resource.as_ref(),
|
||||
EXTENDED.as_bytes(),
|
||||
],
|
||||
)?;
|
||||
let auction_extended_data = AuctionDataExtended::from_account_info(auction_extended_info)?;
|
||||
// add this check because in this instruction we use AuctionDataExtended only for instant_sale_price
|
||||
if auction_extended_data.instant_sale_price.is_some() {
|
||||
auction_extended = Some(auction_extended_data);
|
||||
}
|
||||
}
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
AuctionDataExtended::from_account_info(info)
|
||||
.ok()?
|
||||
.instant_sale_price
|
||||
});
|
||||
|
||||
// Auction either must have ended or bidder pay instant_sale_price
|
||||
if !auction.ended(clock.unix_timestamp)? {
|
||||
if let Some(auction_extended_data) = auction_extended {
|
||||
// we can safely unwrap instant_sale_price because we checked it in if let instruction before
|
||||
if auction.bid_state.amount(bid_index.unwrap())
|
||||
< auction_extended_data.instant_sale_price.unwrap()
|
||||
match instant_sale_price {
|
||||
Some(instant_sale_price)
|
||||
if auction.bid_state.amount(bid_index.unwrap()) < instant_sale_price =>
|
||||
{
|
||||
return Err(AuctionError::InvalidState.into());
|
||||
return Err(AuctionError::InvalidState.into())
|
||||
}
|
||||
} else {
|
||||
return Err(AuctionError::InvalidState.into());
|
||||
None => return Err(AuctionError::InvalidState.into()),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ pub enum MetaplexInstruction {
|
|||
/// 18. `[optional/writable]` Master edition (if Printing type of WinningConfig)
|
||||
/// 19. `[optional/writable]` Reservation list PDA ['metadata', program id, master edition key, 'reservation', auction manager key]
|
||||
/// relative to token metadata program (if Printing type of WinningConfig)
|
||||
/// 20. `[]` Auction extended
|
||||
/// 20. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||
DeprecatedRedeemBid,
|
||||
|
||||
/// Note: This requires that auction manager be in a Running state.
|
||||
|
@ -160,7 +160,7 @@ pub enum MetaplexInstruction {
|
|||
/// after this transaction. Otherwise this account will be ignored.
|
||||
/// 19. `[]` PDA-based Transfer authority to move the tokens from the store to the destination seed ['vault', program_id, vault key]
|
||||
/// but please note that this is a PDA relative to the Token Vault program, with the 'vault' prefix
|
||||
/// 20. `[]` Auction extended
|
||||
/// 20. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||
RedeemFullRightsTransferBid,
|
||||
|
||||
/// Note: This requires that auction manager be in a Running state.
|
||||
|
@ -198,7 +198,7 @@ pub enum MetaplexInstruction {
|
|||
/// 18. `[writable]` The accept payment account for the auction manager
|
||||
/// 19. `[writable]` The token account you will potentially pay for the open edition bid with if necessary
|
||||
/// 20. `[writable]` Participation NFT printing holding account (present on participation_state)
|
||||
/// 21. `[]` Auction extended
|
||||
/// 21. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||
DeprecatedRedeemParticipationBid,
|
||||
|
||||
/// If the auction manager is in Validated state, it can invoke the start command via calling this command here.
|
||||
|
@ -410,7 +410,7 @@ pub enum MetaplexInstruction {
|
|||
/// where edition_number is NOT the edition number you pass in args but actually edition_number = floor(edition/EDITION_MARKER_BIT_SIZE). PDA is relative to token metadata.
|
||||
/// 23. `[signer]` Mint authority of new mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY
|
||||
/// 24. `[]` Metadata account of token in vault
|
||||
/// 25. `[]` Auction extended
|
||||
/// 25. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||
RedeemPrintingV2Bid(RedeemPrintingV2BidArgs),
|
||||
|
||||
/// Permissionless call to redeem the master edition in a given safety deposit for a PrintingV2 winning config to the
|
||||
|
@ -824,6 +824,7 @@ pub fn create_set_store_instruction(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_deprecated_populate_participation_printing_account_instruction(
|
||||
program_id: Pubkey,
|
||||
safety_deposit_token_store: Pubkey,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use solana_program::log::sol_log_compute_units;
|
||||
use spl_auction::processor::BidderMetadata;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
|
@ -176,6 +177,39 @@ pub fn assert_authority_correct(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assert_auction_is_ended_or_valid_instant_sale(
|
||||
auction_info: &AccountInfo,
|
||||
auction_extended_info: Option<&AccountInfo>,
|
||||
bidder_metadata_info: &AccountInfo,
|
||||
win_index: Option<usize>,
|
||||
) -> ProgramResult {
|
||||
if AuctionData::get_state(auction_info)? == AuctionState::Ended {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let instant_sale_price = auction_extended_info
|
||||
.and_then(|info| AuctionDataExtended::get_instant_sale_price(&info.data.borrow()));
|
||||
|
||||
match instant_sale_price {
|
||||
Some(instant_sale_price) => {
|
||||
let winner_bid_price = if let Some(win_index) = win_index {
|
||||
AuctionData::get_winner_bid_amount_at(auction_info, win_index).unwrap()
|
||||
} else {
|
||||
// Possible case in an open auction
|
||||
BidderMetadata::from_account_info(bidder_metadata_info)?.last_bid
|
||||
};
|
||||
|
||||
if winner_bid_price < instant_sale_price {
|
||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
||||
}
|
||||
}
|
||||
None => return Err(MetaplexError::AuctionHasNotEnded.into()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create account almost from scratch, lifted from
|
||||
/// https://github.com/solana-labs/solana-program-library/blob/7d4873c61721aca25464d42cc5ef651a7923ca79/associated-token-account/program/src/processor.rs#L51-L98
|
||||
#[inline(always)]
|
||||
|
@ -605,21 +639,12 @@ pub fn common_redeem_checks(
|
|||
return Err(MetaplexError::AuctionManagerTokenMetadataProgramMismatch.into());
|
||||
}
|
||||
|
||||
if AuctionData::get_state(auction_info)? != AuctionState::Ended {
|
||||
if win_index.is_some() && auction_extended_info.is_some() {
|
||||
if let Some(instant_sale_price) = AuctionDataExtended::get_instant_sale_price(&auction_extended_info.unwrap().data.borrow()) {
|
||||
// we can safely do unwrap here such as existing win_index proves that we can get winner_bid_price
|
||||
let winner_bid_price = AuctionData::get_winner_bid_amount_at(auction_info, win_index.unwrap()).unwrap();
|
||||
if winner_bid_price < instant_sale_price {
|
||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
||||
}
|
||||
} else {
|
||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
||||
}
|
||||
} else {
|
||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
||||
}
|
||||
}
|
||||
assert_auction_is_ended_or_valid_instant_sale(
|
||||
auction_info,
|
||||
auction_extended_info,
|
||||
bidder_metadata_info,
|
||||
win_index,
|
||||
)?;
|
||||
|
||||
// No-op if already set.
|
||||
auction_manager.state.status = AuctionManagerStatus::Disbursing;
|
||||
|
|
Loading…
Reference in New Issue