mpl_auction/processor/
create_auction.rs

1use mem::size_of;
2
3use crate::{
4    errors::AuctionError,
5    processor::{
6        AuctionData, AuctionDataExtended, AuctionName, AuctionState, Bid, BidState, PriceFloor,
7        WinnerLimit, BASE_AUCTION_DATA_SIZE, MAX_AUCTION_DATA_EXTENDED_SIZE,
8    },
9    utils::{assert_derivation, assert_owned_by, create_or_allocate_account_raw},
10    EXTENDED, PREFIX,
11};
12
13use {
14    borsh::{BorshDeserialize, BorshSerialize},
15    solana_program::{
16        account_info::{next_account_info, AccountInfo},
17        clock::UnixTimestamp,
18        entrypoint::ProgramResult,
19        msg,
20        program_error::ProgramError,
21        pubkey::Pubkey,
22    },
23    std::mem,
24};
25
26#[repr(C)]
27#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)]
28pub struct CreateAuctionArgs {
29    /// How many winners are allowed for this auction. See AuctionData.
30    pub winners: WinnerLimit,
31    /// End time is the cut-off point that the auction is forced to end by. See AuctionData.
32    pub end_auction_at: Option<UnixTimestamp>,
33    /// Gap time is how much time after the previous bid where the auction ends. See AuctionData.
34    pub end_auction_gap: Option<UnixTimestamp>,
35    /// Token mint for the SPL token used for bidding.
36    pub token_mint: Pubkey,
37    /// Authority
38    pub authority: Pubkey,
39    /// The resource being auctioned. See AuctionData.
40    pub resource: Pubkey,
41    /// Set a price floor.
42    pub price_floor: PriceFloor,
43    /// Add a tick size increment
44    pub tick_size: Option<u64>,
45    /// Add a minimum percentage increase each bid must meet.
46    pub gap_tick_size_percentage: Option<u8>,
47}
48
49struct Accounts<'a, 'b: 'a> {
50    auction: &'a AccountInfo<'b>,
51    auction_extended: &'a AccountInfo<'b>,
52    payer: &'a AccountInfo<'b>,
53    rent: &'a AccountInfo<'b>,
54    system: &'a AccountInfo<'b>,
55}
56
57fn parse_accounts<'a, 'b: 'a>(
58    program_id: &Pubkey,
59    accounts: &'a [AccountInfo<'b>],
60) -> Result<Accounts<'a, 'b>, ProgramError> {
61    let account_iter = &mut accounts.iter();
62    let accounts = Accounts {
63        payer: next_account_info(account_iter)?,
64        auction: next_account_info(account_iter)?,
65        auction_extended: next_account_info(account_iter)?,
66        rent: next_account_info(account_iter)?,
67        system: next_account_info(account_iter)?,
68    };
69    Ok(accounts)
70}
71
72pub fn create_auction(
73    program_id: &Pubkey,
74    accounts: &[AccountInfo],
75    args: CreateAuctionArgs,
76    instant_sale_price: Option<u64>,
77    name: Option<AuctionName>,
78) -> ProgramResult {
79    msg!("+ Processing CreateAuction");
80    let accounts = parse_accounts(program_id, accounts)?;
81
82    let auction_path = [
83        PREFIX.as_bytes(),
84        program_id.as_ref(),
85        &args.resource.to_bytes(),
86    ];
87
88    // Derive the address we'll store the auction in, and confirm it matches what we expected the
89    // user to provide.
90    let (auction_key, bump) = Pubkey::find_program_address(&auction_path, program_id);
91    if auction_key != *accounts.auction.key {
92        return Err(AuctionError::InvalidAuctionAccount.into());
93    }
94    // The data must be large enough to hold at least the number of winners.
95    let auction_size = match args.winners {
96        WinnerLimit::Capped(n) => {
97            mem::size_of::<Bid>() * BidState::max_array_size_for(n) + BASE_AUCTION_DATA_SIZE
98        }
99        WinnerLimit::Unlimited(_) => BASE_AUCTION_DATA_SIZE,
100    };
101
102    let bid_state = match args.winners {
103        WinnerLimit::Capped(n) => BidState::new_english(n),
104        WinnerLimit::Unlimited(_) => BidState::new_open_edition(),
105    };
106
107    if let Some(gap_tick) = args.gap_tick_size_percentage {
108        if gap_tick > 100 {
109            return Err(AuctionError::InvalidGapTickSizePercentage.into());
110        }
111    }
112
113    // Create auction account with enough space for a winner tracking.
114    create_or_allocate_account_raw(
115        *program_id,
116        accounts.auction,
117        accounts.rent,
118        accounts.system,
119        accounts.payer,
120        auction_size,
121        &[
122            PREFIX.as_bytes(),
123            program_id.as_ref(),
124            &args.resource.to_bytes(),
125            &[bump],
126        ],
127    )?;
128
129    let auction_ext_bump = assert_derivation(
130        program_id,
131        accounts.auction_extended,
132        &[
133            PREFIX.as_bytes(),
134            program_id.as_ref(),
135            &args.resource.to_bytes(),
136            EXTENDED.as_bytes(),
137        ],
138    )?;
139
140    create_or_allocate_account_raw(
141        *program_id,
142        accounts.auction_extended,
143        accounts.rent,
144        accounts.system,
145        accounts.payer,
146        MAX_AUCTION_DATA_EXTENDED_SIZE,
147        &[
148            PREFIX.as_bytes(),
149            program_id.as_ref(),
150            &args.resource.to_bytes(),
151            EXTENDED.as_bytes(),
152            &[auction_ext_bump],
153        ],
154    )?;
155
156    // Configure extended
157    AuctionDataExtended {
158        total_uncancelled_bids: 0,
159        tick_size: args.tick_size,
160        gap_tick_size_percentage: args.gap_tick_size_percentage,
161        instant_sale_price,
162        name,
163    }
164    .serialize(&mut *accounts.auction_extended.data.borrow_mut())?;
165
166    // Configure Auction.
167    AuctionData {
168        authority: args.authority,
169        bid_state: bid_state,
170        end_auction_at: args.end_auction_at,
171        end_auction_gap: args.end_auction_gap,
172        ended_at: None,
173        last_bid: None,
174        price_floor: args.price_floor,
175        state: AuctionState::create(),
176        token_mint: args.token_mint,
177    }
178    .serialize(&mut *accounts.auction.data.borrow_mut())?;
179
180    Ok(())
181}