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
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { findProgramAddress } from '../utils';
|
||||||
export const AUCTION_PREFIX = 'auction';
|
export const AUCTION_PREFIX = 'auction';
|
||||||
export const METADATA = 'metadata';
|
export const METADATA = 'metadata';
|
||||||
export const EXTENDED = 'extended';
|
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 {
|
export enum AuctionState {
|
||||||
Created = 0,
|
Created = 0,
|
||||||
|
|
|
@ -31,20 +31,11 @@ pub enum AuctionInstruction {
|
||||||
/// Create a new auction account bound to a resource, initially in a pending state.
|
/// 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.
|
/// 0. `[signer]` The account creating the auction, which is authorised to make changes.
|
||||||
/// 1. `[writable]` Uninitialized auction account.
|
/// 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
|
/// 3. `[]` Rent sysvar
|
||||||
/// 4. `[]` System account
|
/// 4. `[]` System account
|
||||||
CreateAuction(CreateAuctionArgs),
|
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.
|
/// Move SPL tokens from winning bid to the destination account.
|
||||||
/// 0. `[writable]` The destination account
|
/// 0. `[writable]` The destination account
|
||||||
/// 1. `[writable]` The bidder pot token account
|
/// 1. `[writable]` The bidder pot token account
|
||||||
|
@ -55,7 +46,7 @@ pub enum AuctionInstruction {
|
||||||
/// 6. `[]` Token mint of the auction
|
/// 6. `[]` Token mint of the auction
|
||||||
/// 7. `[]` Clock sysvar
|
/// 7. `[]` Clock sysvar
|
||||||
/// 8. `[]` Token program
|
/// 8. `[]` Token program
|
||||||
/// 9. `[]` Auction extended
|
/// 9. `[]` Auction extended (pda relative to auction of ['auction', program id, vault key, 'extended'])
|
||||||
ClaimBid(ClaimBidArgs),
|
ClaimBid(ClaimBidArgs),
|
||||||
|
|
||||||
/// Ends an auction, regardless of end timing conditions
|
/// Ends an auction, regardless of end timing conditions
|
||||||
|
@ -85,6 +76,15 @@ pub enum AuctionInstruction {
|
||||||
/// 11. `[]` System program
|
/// 11. `[]` System program
|
||||||
/// 12. `[]` SPL Token Program
|
/// 12. `[]` SPL Token Program
|
||||||
PlaceBid(PlaceBidArgs),
|
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.
|
/// Creates an CreateAuction instruction.
|
||||||
|
|
|
@ -370,6 +370,16 @@ impl AuctionData {
|
||||||
self.bid_state.winner_at(idx)
|
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(
|
pub fn place_bid(
|
||||||
&mut self,
|
&mut self,
|
||||||
bid: Bid,
|
bid: Bid,
|
||||||
|
@ -394,6 +404,7 @@ impl AuctionData {
|
||||||
PriceFloor::MinimumPrice(min) => min[0],
|
PriceFloor::MinimumPrice(min) => min[0],
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.bid_state.place_bid(
|
self.bid_state.place_bid(
|
||||||
bid,
|
bid,
|
||||||
tick_size,
|
tick_size,
|
||||||
|
@ -401,7 +412,11 @@ impl AuctionData {
|
||||||
minimum,
|
minimum,
|
||||||
instant_sale_price,
|
instant_sale_price,
|
||||||
&mut self.state,
|
&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);
|
let max_size = BidState::max_array_size_for(*max);
|
||||||
|
|
||||||
if bids.len() > max_size {
|
if bids.len() > max_size {
|
||||||
|
@ -702,6 +706,17 @@ impl BidState {
|
||||||
BidState::OpenEdition { bids, max } => None,
|
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)]
|
#[repr(C)]
|
||||||
|
|
|
@ -132,36 +132,34 @@ pub fn claim_bid(
|
||||||
return Err(AuctionError::InvalidState.into());
|
return Err(AuctionError::InvalidState.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut auction_extended: Option<AuctionDataExtended> = None;
|
let instant_sale_price = accounts.auction_extended.and_then(|info| {
|
||||||
if let Some(auction_extended_info) = accounts.auction_extended {
|
|
||||||
assert_derivation(
|
assert_derivation(
|
||||||
program_id,
|
program_id,
|
||||||
auction_extended_info,
|
info,
|
||||||
&[
|
&[
|
||||||
PREFIX.as_bytes(),
|
PREFIX.as_bytes(),
|
||||||
program_id.as_ref(),
|
program_id.as_ref(),
|
||||||
args.resource.as_ref(),
|
args.resource.as_ref(),
|
||||||
EXTENDED.as_bytes(),
|
EXTENDED.as_bytes(),
|
||||||
],
|
],
|
||||||
)?;
|
)
|
||||||
let auction_extended_data = AuctionDataExtended::from_account_info(auction_extended_info)?;
|
.ok()?;
|
||||||
// add this check because in this instruction we use AuctionDataExtended only for instant_sale_price
|
|
||||||
if auction_extended_data.instant_sale_price.is_some() {
|
AuctionDataExtended::from_account_info(info)
|
||||||
auction_extended = Some(auction_extended_data);
|
.ok()?
|
||||||
}
|
.instant_sale_price
|
||||||
}
|
});
|
||||||
|
|
||||||
// Auction either must have ended or bidder pay instant_sale_price
|
// Auction either must have ended or bidder pay instant_sale_price
|
||||||
if !auction.ended(clock.unix_timestamp)? {
|
if !auction.ended(clock.unix_timestamp)? {
|
||||||
if let Some(auction_extended_data) = auction_extended {
|
match instant_sale_price {
|
||||||
// we can safely unwrap instant_sale_price because we checked it in if let instruction before
|
Some(instant_sale_price)
|
||||||
if auction.bid_state.amount(bid_index.unwrap())
|
if auction.bid_state.amount(bid_index.unwrap()) < instant_sale_price =>
|
||||||
< auction_extended_data.instant_sale_price.unwrap()
|
|
||||||
{
|
{
|
||||||
return Err(AuctionError::InvalidState.into());
|
return Err(AuctionError::InvalidState.into())
|
||||||
}
|
}
|
||||||
} else {
|
None => return Err(AuctionError::InvalidState.into()),
|
||||||
return Err(AuctionError::InvalidState.into());
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ pub enum MetaplexInstruction {
|
||||||
/// 18. `[optional/writable]` Master edition (if Printing type of WinningConfig)
|
/// 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]
|
/// 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)
|
/// 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,
|
DeprecatedRedeemBid,
|
||||||
|
|
||||||
/// Note: This requires that auction manager be in a Running state.
|
/// 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.
|
/// 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]
|
/// 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
|
/// 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,
|
RedeemFullRightsTransferBid,
|
||||||
|
|
||||||
/// Note: This requires that auction manager be in a Running state.
|
/// 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
|
/// 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
|
/// 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)
|
/// 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,
|
DeprecatedRedeemParticipationBid,
|
||||||
|
|
||||||
/// If the auction manager is in Validated state, it can invoke the start command via calling this command here.
|
/// 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.
|
/// 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
|
/// 23. `[signer]` Mint authority of new mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY
|
||||||
/// 24. `[]` Metadata account of token in vault
|
/// 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),
|
RedeemPrintingV2Bid(RedeemPrintingV2BidArgs),
|
||||||
|
|
||||||
/// Permissionless call to redeem the master edition in a given safety deposit for a PrintingV2 winning config to the
|
/// 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(
|
pub fn create_deprecated_populate_participation_printing_account_instruction(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
safety_deposit_token_store: Pubkey,
|
safety_deposit_token_store: Pubkey,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use solana_program::log::sol_log_compute_units;
|
use solana_program::log::sol_log_compute_units;
|
||||||
|
use spl_auction::processor::BidderMetadata;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
@ -176,6 +177,39 @@ pub fn assert_authority_correct(
|
||||||
|
|
||||||
Ok(())
|
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
|
/// 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
|
/// https://github.com/solana-labs/solana-program-library/blob/7d4873c61721aca25464d42cc5ef651a7923ca79/associated-token-account/program/src/processor.rs#L51-L98
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -605,21 +639,12 @@ pub fn common_redeem_checks(
|
||||||
return Err(MetaplexError::AuctionManagerTokenMetadataProgramMismatch.into());
|
return Err(MetaplexError::AuctionManagerTokenMetadataProgramMismatch.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if AuctionData::get_state(auction_info)? != AuctionState::Ended {
|
assert_auction_is_ended_or_valid_instant_sale(
|
||||||
if win_index.is_some() && auction_extended_info.is_some() {
|
auction_info,
|
||||||
if let Some(instant_sale_price) = AuctionDataExtended::get_instant_sale_price(&auction_extended_info.unwrap().data.borrow()) {
|
auction_extended_info,
|
||||||
// we can safely do unwrap here such as existing win_index proves that we can get winner_bid_price
|
bidder_metadata_info,
|
||||||
let winner_bid_price = AuctionData::get_winner_bid_amount_at(auction_info, win_index.unwrap()).unwrap();
|
win_index,
|
||||||
if winner_bid_price < instant_sale_price {
|
)?;
|
||||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(MetaplexError::AuctionHasNotEnded.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No-op if already set.
|
// No-op if already set.
|
||||||
auction_manager.state.status = AuctionManagerStatus::Disbursing;
|
auction_manager.state.status = AuctionManagerStatus::Disbursing;
|
||||||
|
|
Loading…
Reference in New Issue