ywpl_plex/
state.rs

1use {
2    crate::{
3        deprecated_state::AuctionManagerV1, error::MetaplexError, utils::try_from_slice_checked,
4    },
5    arrayref::{array_mut_ref, array_ref, mut_array_refs},
6    borsh::{BorshDeserialize, BorshSerialize},
7    ywpl_auction::processor::AuctionData,
8    mpl_token_metadata::state::Metadata,
9    ywpl_token_vault::state::SafetyDepositBox,
10    solana_program::{
11        account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
12        pubkey::Pubkey,
13    },
14    std::cell::{Ref, RefMut},
15};
16/// prefix used for PDAs to avoid certain collision attacks (https://en.wikipedia.org/wiki/Collision_attack#Chosen-prefix_collision_attack)
17pub const PREFIX: &str = "metaplex";
18pub const TOTALS: &str = "totals";
19pub const INDEX: &str = "index";
20pub const CACHE: &str = "cache";
21pub const CONFIG: &str = "config";
22pub const BASE_TRACKER_SIZE: usize = 1 + 1 + 1 + 4;
23
24pub const MAX_INDEXED_ELEMENTS: usize = 100;
25pub const MAX_STORE_INDEXER_SIZE: usize = 1 + //key
2632 + //store
278 + //page
284 + // how many elements are in the vec
2932*MAX_INDEXED_ELEMENTS; // size of indexed auction keys
30
31pub const MAX_METADATA_PER_CACHE: usize = 10;
32pub const MAX_AUCTION_CACHE_SIZE: usize = 1 + //key
3332 + //store
348 + // timestamp
354 + // metadata count
3632*MAX_METADATA_PER_CACHE + //metadata
3732 + // auction
3832 + // vault
3932; // auction manager
40
41pub const MAX_AUCTION_MANAGER_V2_SIZE: usize = 1 + //key
4232 + // store
4332 + // authority
4432 + // auction
4532 + // vault
4632 + // accept_payment
471 + // has participation
481 + //status
498 + // winning configs validated
50200; // padding
51pub const MAX_STORE_SIZE: usize = 2 + // Store Version Key 
5232 + // Auction Program Key
5332 + // Token Vault Program Key
5432 + // Token Metadata Program Key
5532 + // Token Program Key
56100; // Padding;
57pub const MAX_STORE_CONFIG_V1_SIZE: usize = 2 + // StoreConfig Version Key 
58200 + // Settings Uri Len
59100; // Padding;
60pub const MAX_WHITELISTED_CREATOR_SIZE: usize = 2 + 32 + 10;
61pub const MAX_PAYOUT_TICKET_SIZE: usize = 1 + 32 + 8;
62pub const MAX_BID_REDEMPTION_TICKET_SIZE: usize = 3;
63pub const MAX_AUTHORITY_LOOKUP_SIZE: usize = 33;
64pub const MAX_PRIZE_TRACKING_TICKET_SIZE: usize = 1 + 32 + 8 + 8 + 8 + 50;
65pub const BASE_SAFETY_CONFIG_SIZE: usize = 1 +// Key
66 32 + // auction manager lookup
67 8 + // order
68 1 + // winning config type
69 1 + // amount tuple type
70 1 + // length tuple type
71 4 + // u32 for amount range vec
72 1 + // participation config option
73 1 + // winning constraint
74 1 + // non winning constraint
75 9 + // fixed price + option of it
76 1 + // participation state option
77 8 + // collected to accept payment
78 20; // padding
79
80#[repr(C)]
81#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug, Copy)]
82pub enum Key {
83    Uninitialized,
84    OriginalAuthorityLookupV1,
85    BidRedemptionTicketV1,
86    StoreV1,
87    WhitelistedCreatorV1,
88    PayoutTicketV1,
89    SafetyDepositValidationTicketV1,
90    AuctionManagerV1,
91    PrizeTrackingTicketV1,
92    SafetyDepositConfigV1,
93    AuctionManagerV2,
94    BidRedemptionTicketV2,
95    AuctionWinnerTokenTypeTrackerV1,
96    StoreIndexerV1,
97    AuctionCacheV1,
98    StoreConfigV1,
99}
100
101pub struct CommonWinningIndexChecks<'a> {
102    pub safety_deposit_info: &'a AccountInfo<'a>,
103    pub winning_index: usize,
104    pub auction_manager_v1_ignore_claim: bool,
105    pub safety_deposit_config_info: Option<&'a AccountInfo<'a>>,
106}
107
108pub struct PrintingV2CalculationChecks<'a> {
109    pub safety_deposit_info: &'a AccountInfo<'a>,
110    pub winning_index: usize,
111    pub auction_manager_v1_ignore_claim: bool,
112    pub safety_deposit_config_info: Option<&'a AccountInfo<'a>>,
113    pub short_circuit_total: bool,
114    pub edition_offset: u64,
115    pub winners: usize,
116}
117
118pub struct CommonWinningIndexReturn {
119    pub amount: u64,
120    pub winning_config_type: WinningConfigType,
121    pub winning_config_item_index: Option<usize>,
122}
123
124pub struct PrintingV2CalculationCheckReturn {
125    pub expected_redemptions: u64,
126    pub winning_config_type: WinningConfigType,
127    pub winning_config_item_index: Option<usize>,
128}
129
130pub trait AuctionManager {
131    fn key(&self) -> Key;
132    fn store(&self) -> Pubkey;
133    fn authority(&self) -> Pubkey;
134    fn auction(&self) -> Pubkey;
135    fn vault(&self) -> Pubkey;
136    fn accept_payment(&self) -> Pubkey;
137    fn status(&self) -> AuctionManagerStatus;
138    fn set_status(&mut self, status: AuctionManagerStatus);
139    fn configs_validated(&self) -> u64;
140    fn set_configs_validated(&mut self, new_configs_validated: u64);
141    fn save(&self, account: &AccountInfo) -> ProgramResult;
142    fn fast_save(
143        &self,
144        account: &AccountInfo,
145        winning_config_index: usize,
146        winning_config_item_index: usize,
147    );
148    fn common_winning_index_checks(
149        &self,
150        args: CommonWinningIndexChecks,
151    ) -> Result<CommonWinningIndexReturn, ProgramError>;
152
153    fn printing_v2_calculation_checks(
154        &self,
155        args: PrintingV2CalculationChecks,
156    ) -> Result<PrintingV2CalculationCheckReturn, ProgramError>;
157
158    fn get_participation_config(
159        &self,
160        safety_deposit_config_info: &AccountInfo,
161    ) -> Result<ParticipationConfigV2, ProgramError>;
162
163    fn add_to_collected_payment(
164        &mut self,
165        safety_deposit_config_info: &AccountInfo,
166        price: u64,
167    ) -> ProgramResult;
168
169    fn assert_legacy_printing_token_match(&self, account: &AccountInfo) -> ProgramResult;
170    fn get_max_bids_allowed_before_removal_is_stopped(
171        &self,
172        safety_deposit_box_order: u64,
173        safety_deposit_config_info: Option<&AccountInfo>,
174    ) -> Result<usize, ProgramError>;
175
176    fn assert_is_valid_master_edition_v2_safety_deposit(
177        &self,
178        safety_deposit_box_order: u64,
179        safety_deposit_config_info: Option<&AccountInfo>,
180    ) -> ProgramResult;
181
182    fn mark_bid_as_claimed(&mut self, winner_index: usize) -> ProgramResult;
183
184    fn assert_all_bids_claimed(&self, auction: &AuctionData) -> ProgramResult;
185
186    fn get_number_of_unique_token_types_for_this_winner(
187        &self,
188        winner_index: usize,
189        auction_token_tracker_info: Option<&AccountInfo>,
190    ) -> Result<u128, ProgramError>;
191
192    fn get_collected_to_accept_payment(
193        &self,
194        safety_deposit_config_info: Option<&AccountInfo>,
195    ) -> Result<u128, ProgramError>;
196
197    fn get_primary_sale_happened(
198        &self,
199        metadata: &Metadata,
200        winning_config_index: Option<u8>,
201        winning_config_item_index: Option<u8>,
202    ) -> Result<bool, ProgramError>;
203
204    fn assert_winning_config_safety_deposit_validity(
205        &self,
206        safety_deposit: &SafetyDepositBox,
207        winning_config_index: Option<u8>,
208        winning_config_item_index: Option<u8>,
209    ) -> ProgramResult;
210}
211
212pub fn get_auction_manager(account: &AccountInfo) -> Result<Box<dyn AuctionManager>, ProgramError> {
213    let version = account.data.borrow()[0];
214
215    // For some reason when converting Key to u8 here, it becomes unreachable. Use direct constant instead.
216    match version {
217        7 => return Ok(Box::new(AuctionManagerV1::from_account_info(account)?)),
218        10 => return Ok(Box::new(AuctionManagerV2::from_account_info(account)?)),
219        _ => return Err(MetaplexError::DataTypeMismatch.into()),
220    };
221}
222#[repr(C)]
223#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)]
224pub struct AuctionManagerV2 {
225    pub key: Key,
226
227    pub store: Pubkey,
228
229    pub authority: Pubkey,
230
231    pub auction: Pubkey,
232
233    pub vault: Pubkey,
234
235    pub accept_payment: Pubkey,
236
237    pub state: AuctionManagerStateV2,
238}
239
240impl AuctionManager for AuctionManagerV2 {
241    fn key(&self) -> Key {
242        self.key
243    }
244
245    fn store(&self) -> Pubkey {
246        self.store
247    }
248
249    fn authority(&self) -> Pubkey {
250        self.authority
251    }
252
253    fn auction(&self) -> Pubkey {
254        self.auction
255    }
256
257    fn vault(&self) -> Pubkey {
258        self.vault
259    }
260
261    fn accept_payment(&self) -> Pubkey {
262        self.accept_payment
263    }
264
265    fn status(&self) -> AuctionManagerStatus {
266        self.state.status
267    }
268
269    fn fast_save(
270        &self,
271        account: &AccountInfo,
272        _winning_config_index: usize,
273        _winning_config_item_index: usize,
274    ) {
275        let mut data = account.data.borrow_mut();
276        data[161] = self.state.status as u8;
277    }
278
279    fn common_winning_index_checks(
280        &self,
281        args: CommonWinningIndexChecks,
282    ) -> Result<CommonWinningIndexReturn, ProgramError> {
283        let CommonWinningIndexChecks {
284            safety_deposit_config_info,
285            safety_deposit_info: _s,
286            winning_index,
287            auction_manager_v1_ignore_claim: _a,
288        } = args;
289
290        if let Some(config) = safety_deposit_config_info {
291            Ok(CommonWinningIndexReturn {
292                amount: SafetyDepositConfig::find_amount_and_cumulative_offset(
293                    config,
294                    winning_index as u64,
295                    None,
296                )?
297                .amount,
298                winning_config_type: SafetyDepositConfig::get_winning_config_type(config)?,
299                // not used
300                winning_config_item_index: Some(0),
301            })
302        } else {
303            return Err(MetaplexError::InvalidOperation.into());
304        }
305    }
306
307    fn printing_v2_calculation_checks(
308        &self,
309        args: PrintingV2CalculationChecks,
310    ) -> Result<PrintingV2CalculationCheckReturn, ProgramError> {
311        let PrintingV2CalculationChecks {
312            safety_deposit_config_info,
313            safety_deposit_info: _s,
314            winning_index,
315            auction_manager_v1_ignore_claim: _a,
316            short_circuit_total: _ss,
317            edition_offset,
318            winners,
319        } = args;
320        if let Some(config) = safety_deposit_config_info {
321            let derived_results = SafetyDepositConfig::find_amount_and_cumulative_offset(
322                config,
323                winning_index as u64,
324                Some(winners),
325            )?;
326
327            let edition_offset_min = derived_results
328                .cumulative_amount
329                .checked_add(1)
330                .ok_or(MetaplexError::NumericalOverflowError)?;
331            let edition_offset_max = edition_offset_min
332                .checked_add(derived_results.amount)
333                .ok_or(MetaplexError::NumericalOverflowError)?;
334            if edition_offset < edition_offset_min || edition_offset >= edition_offset_max {
335                return Err(MetaplexError::InvalidEditionNumber.into());
336            }
337
338            Ok(PrintingV2CalculationCheckReturn {
339                // NOTE this total will be WRONG if short circuit is TRUE. But also it wont be USED if it's true!
340                expected_redemptions: derived_results.total_amount,
341                winning_config_type: SafetyDepositConfig::get_winning_config_type(config)?,
342                // not used
343                winning_config_item_index: Some(0),
344            })
345        } else {
346            return Err(MetaplexError::InvalidOperation.into());
347        }
348    }
349
350    fn set_status(&mut self, status: AuctionManagerStatus) {
351        self.state.status = status
352    }
353
354    fn configs_validated(&self) -> u64 {
355        self.state.safety_config_items_validated
356    }
357
358    fn set_configs_validated(&mut self, new_configs_validated: u64) {
359        self.state.safety_config_items_validated = new_configs_validated
360    }
361
362    fn save(&self, account: &AccountInfo) -> ProgramResult {
363        self.serialize(&mut *account.data.borrow_mut())?;
364        Ok(())
365    }
366
367    fn get_participation_config(
368        &self,
369        safety_deposit_config_info: &AccountInfo,
370    ) -> Result<ParticipationConfigV2, ProgramError> {
371        let safety_config = SafetyDepositConfig::from_account_info(safety_deposit_config_info)?;
372        if let Some(p_config) = safety_config.participation_config {
373            Ok(p_config)
374        } else {
375            return Err(MetaplexError::NotEligibleForParticipation.into());
376        }
377    }
378
379    fn add_to_collected_payment(
380        &mut self,
381        safety_deposit_config_info: &AccountInfo,
382        price: u64,
383    ) -> ProgramResult {
384        let mut safety_config = SafetyDepositConfig::from_account_info(safety_deposit_config_info)?;
385
386        if let Some(state) = &safety_config.participation_state {
387            // Can't really edit something behind an Option reference...
388            // just make new one.
389            safety_config.participation_state = Some(ParticipationStateV2 {
390                collected_to_accept_payment: state
391                    .collected_to_accept_payment
392                    .checked_add(price)
393                    .ok_or(MetaplexError::NumericalOverflowError)?,
394            });
395            safety_config.save_participation_state(safety_deposit_config_info)
396        }
397
398        Ok(())
399    }
400
401    fn assert_legacy_printing_token_match(&self, _account: &AccountInfo) -> ProgramResult {
402        // You cannot use MEV1s with auth tokens with V2 auction managers, so if somehow this is called,
403        // throw an error.
404        return Err(MetaplexError::PrintingAuthorizationTokenAccountMismatch.into());
405    }
406
407    fn get_max_bids_allowed_before_removal_is_stopped(
408        &self,
409        _safety_deposit_box_order: u64,
410        safety_deposit_config_info: Option<&AccountInfo>,
411    ) -> Result<usize, ProgramError> {
412        if let Some(config) = safety_deposit_config_info {
413            let safety_config = SafetyDepositConfig::from_account_info(config)?;
414            let mut current_offset: u64 = 0;
415            for n in safety_config.amount_ranges {
416                if n.0 > 0 {
417                    return Ok(current_offset as usize);
418                } else {
419                    current_offset = current_offset
420                        .checked_add(n.1)
421                        .ok_or(MetaplexError::NumericalOverflowError)?;
422                }
423            }
424
425            Ok(0)
426        } else {
427            return Err(MetaplexError::InvalidOperation.into());
428        }
429    }
430
431    fn assert_is_valid_master_edition_v2_safety_deposit(
432        &self,
433        _safety_deposit_box_order: u64,
434        safety_deposit_config_info: Option<&AccountInfo>,
435    ) -> ProgramResult {
436        if let Some(config) = safety_deposit_config_info {
437            let safety_config = SafetyDepositConfig::from_account_info(config)?;
438
439            if safety_config.winning_config_type != WinningConfigType::PrintingV2
440                && safety_config.winning_config_type != WinningConfigType::Participation
441            {
442                return Err(MetaplexError::InvalidOperation.into());
443            }
444
445            Ok(())
446        } else {
447            return Err(MetaplexError::InvalidOperation.into());
448        }
449    }
450
451    fn mark_bid_as_claimed(&mut self, _winner_index: usize) -> ProgramResult {
452        self.state.bids_pushed_to_accept_payment = self
453            .state
454            .bids_pushed_to_accept_payment
455            .checked_add(1)
456            .ok_or(MetaplexError::NumericalOverflowError)?;
457
458        Ok(())
459    }
460
461    fn assert_all_bids_claimed(&self, auction: &AuctionData) -> ProgramResult {
462        if self.state.bids_pushed_to_accept_payment != auction.num_winners() {
463            return Err(MetaplexError::NotAllBidsClaimed.into());
464        }
465
466        Ok(())
467    }
468
469    fn get_number_of_unique_token_types_for_this_winner(
470        &self,
471        winner_index: usize,
472        auction_token_tracker_info: Option<&AccountInfo>,
473    ) -> Result<u128, ProgramError> {
474        if let Some(tracker_info) = auction_token_tracker_info {
475            let tracker = AuctionWinnerTokenTypeTracker::from_account_info(tracker_info)?;
476            let mut start: u64 = 0;
477            for range in tracker.amount_ranges {
478                let end = start
479                    .checked_add(range.1)
480                    .ok_or(MetaplexError::NumericalOverflowError)?;
481                if winner_index >= start as usize && winner_index < end as usize {
482                    return Ok(range.0 as u128);
483                } else {
484                    start = end
485                }
486            }
487
488            return Err(MetaplexError::NoTokensForThisWinner.into());
489        } else {
490            return Err(MetaplexError::InvalidOperation.into());
491        }
492    }
493
494    fn get_collected_to_accept_payment(
495        &self,
496        safety_deposit_config_info: Option<&AccountInfo>,
497    ) -> Result<u128, ProgramError> {
498        if let Some(config) = safety_deposit_config_info {
499            let parsed = SafetyDepositConfig::from_account_info(config)?;
500            if let Some(p_state) = parsed.participation_state {
501                Ok(p_state.collected_to_accept_payment as u128)
502            } else {
503                Ok(0)
504            }
505        } else {
506            return Err(MetaplexError::InvalidOperation.into());
507        }
508    }
509
510    fn get_primary_sale_happened(
511        &self,
512        metadata: &Metadata,
513        _winning_config_index: Option<u8>,
514        _winning_config_item_index: Option<u8>,
515    ) -> Result<bool, ProgramError> {
516        // Since auction v2s only support mev2s, and mev2s are always inside
517        // the auction all the time, this is a valid thing to do.
518        Ok(metadata.primary_sale_happened)
519    }
520
521    fn assert_winning_config_safety_deposit_validity(
522        &self,
523        _safety_deposit: &SafetyDepositBox,
524        _winning_config_index: Option<u8>,
525        _winning_config_item_index: Option<u8>,
526    ) -> ProgramResult {
527        // Noop, we dont have winning configs anymore
528        Ok(())
529    }
530}
531
532impl AuctionManagerV2 {
533    pub fn from_account_info(a: &AccountInfo) -> Result<AuctionManagerV2, ProgramError> {
534        let am: AuctionManagerV2 = try_from_slice_checked(
535            &a.data.borrow_mut(),
536            Key::AuctionManagerV2,
537            MAX_AUCTION_MANAGER_V2_SIZE,
538        )?;
539
540        Ok(am)
541    }
542}
543
544#[repr(C)]
545#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)]
546pub struct AuctionManagerStateV2 {
547    pub status: AuctionManagerStatus,
548    /// When all configs are validated the auction is started and auction manager moves to Running
549    pub safety_config_items_validated: u64,
550    /// how many bids have been pushed to accept payment
551    pub bids_pushed_to_accept_payment: u64,
552
553    pub has_participation: bool,
554}
555
556#[repr(C)]
557#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
558pub struct ParticipationStateV2 {
559    /// We have this variable below to keep track in the case of the participation NFTs, whose
560    /// income will trickle in over time, how much the artists have in the escrow account and
561    /// how much would/should be owed to them if they try to claim it relative to the winning bids.
562    /// It's  abit tougher than a straightforward bid which has a price attached to it, because
563    /// there are many bids of differing amounts (in the case of GivenForBidPrice) and they dont all
564    /// come in at one time, so this little ledger here keeps track.
565    pub collected_to_accept_payment: u64,
566}
567
568#[repr(C)]
569#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
570pub struct ParticipationConfigV2 {
571    /// Setups:
572    /// 1. Winners get participation + not charged extra
573    /// 2. Winners dont get participation prize
574    pub winner_constraint: WinningConstraint,
575
576    /// Setups:
577    /// 1. Losers get prize for free
578    /// 2. Losers get prize but pay fixed price
579    /// 3. Losers get prize but pay bid price
580    pub non_winning_constraint: NonWinningConstraint,
581
582    /// Setting this field disconnects the participation prizes price from the bid. Any bid you submit, regardless
583    /// of amount, charges you the same fixed price.
584    pub fixed_price: Option<u64>,
585}
586
587#[repr(C)]
588#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug, Copy)]
589pub enum WinningConstraint {
590    NoParticipationPrize,
591    ParticipationPrizeGiven,
592}
593
594#[repr(C)]
595#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug, Copy)]
596pub enum NonWinningConstraint {
597    NoParticipationPrize,
598    GivenForFixedPrice,
599    GivenForBidPrice,
600}
601
602#[repr(C)]
603#[derive(Clone, PartialEq, BorshSerialize, BorshDeserialize, Copy, Debug)]
604pub enum WinningConfigType {
605    /// You may be selling your one-of-a-kind NFT for the first time, but not it's accompanying Metadata,
606    /// of which you would like to retain ownership. You get 100% of the payment the first sale, then
607    /// royalties forever after.
608    ///
609    /// You may be re-selling something like a Limited/Open Edition print from another auction,
610    /// a master edition record token by itself (Without accompanying metadata/printing ownership), etc.
611    /// This means artists will get royalty fees according to the top level royalty % on the metadata
612    /// split according to their percentages of contribution.
613    ///
614    /// No metadata ownership is transferred in this instruction, which means while you may be transferring
615    /// the token for a limited/open edition away, you would still be (nominally) the owner of the limited edition
616    /// metadata, though it confers no rights or privileges of any kind.
617    TokenOnlyTransfer,
618    /// Means you are auctioning off the master edition record and it's metadata ownership as well as the
619    /// token itself. The other person will be able to mint authorization tokens and make changes to the
620    /// artwork.
621    FullRightsTransfer,
622    /// Means you are using authorization tokens to print off editions during the auction using
623    /// from a MasterEditionV1
624    PrintingV1,
625    /// Means you are using the MasterEditionV2 to print off editions
626    PrintingV2,
627    /// Means you are using a MasterEditionV2 as a participation prize.
628    Participation,
629}
630
631#[repr(C)]
632#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Copy)]
633pub enum AuctionManagerStatus {
634    Initialized,
635    Validated,
636    Running,
637    Disbursing,
638    Finished,
639}
640
641#[repr(C)]
642#[derive(Clone, BorshSerialize, BorshDeserialize, Copy)]
643pub struct OriginalAuthorityLookup {
644    pub key: Key,
645    pub original_authority: Pubkey,
646}
647
648impl OriginalAuthorityLookup {
649    pub fn from_account_info(a: &AccountInfo) -> Result<OriginalAuthorityLookup, ProgramError> {
650        let pt: OriginalAuthorityLookup = try_from_slice_checked(
651            &a.data.borrow_mut(),
652            Key::OriginalAuthorityLookupV1,
653            MAX_AUTHORITY_LOOKUP_SIZE,
654        )?;
655
656        Ok(pt)
657    }
658}
659
660#[repr(C)]
661#[derive(Clone, BorshSerialize, BorshDeserialize, Copy)]
662pub struct PayoutTicket {
663    pub key: Key,
664    pub recipient: Pubkey,
665    pub amount_paid: u64,
666}
667
668impl PayoutTicket {
669    pub fn from_account_info(a: &AccountInfo) -> Result<PayoutTicket, ProgramError> {
670        let pt: PayoutTicket = try_from_slice_checked(
671            &a.data.borrow_mut(),
672            Key::PayoutTicketV1,
673            MAX_PAYOUT_TICKET_SIZE,
674        )?;
675
676        Ok(pt)
677    }
678}
679
680#[repr(C)]
681#[derive(Clone, BorshSerialize, BorshDeserialize)]
682pub struct StoreIndexer {
683    pub key: Key,
684    pub store: Pubkey,
685    pub page: u64,
686    pub auction_caches: Vec<Pubkey>,
687}
688
689impl StoreIndexer {
690    pub fn from_account_info(a: &AccountInfo) -> Result<StoreIndexer, ProgramError> {
691        let store: StoreIndexer = try_from_slice_checked(
692            &a.data.borrow_mut(),
693            Key::StoreIndexerV1,
694            MAX_STORE_INDEXER_SIZE,
695        )?;
696
697        Ok(store)
698    }
699}
700
701#[repr(C)]
702#[derive(Clone, BorshSerialize, BorshDeserialize)]
703pub struct AuctionCache {
704    pub key: Key,
705    pub store: Pubkey,
706    pub timestamp: i64,
707    pub metadata: Vec<Pubkey>,
708    pub auction: Pubkey,
709    pub vault: Pubkey,
710    pub auction_manager: Pubkey,
711}
712
713impl AuctionCache {
714    pub fn from_account_info(a: &AccountInfo) -> Result<AuctionCache, ProgramError> {
715        let store: AuctionCache = try_from_slice_checked(
716            &a.data.borrow_mut(),
717            Key::AuctionCacheV1,
718            MAX_AUCTION_CACHE_SIZE,
719        )?;
720
721        Ok(store)
722    }
723}
724#[repr(C)]
725#[derive(Clone, BorshSerialize, BorshDeserialize, Copy)]
726pub struct Store {
727    pub key: Key,
728    pub public: bool,
729    pub auction_program: Pubkey,
730    pub token_vault_program: Pubkey,
731    pub token_metadata_program: Pubkey,
732    pub token_program: Pubkey,
733}
734
735impl Store {
736    pub fn from_account_info(a: &AccountInfo) -> Result<Store, ProgramError> {
737        let store: Store =
738            try_from_slice_checked(&a.data.borrow_mut(), Key::StoreV1, MAX_STORE_SIZE)?;
739
740        Ok(store)
741    }
742}
743#[repr(C)]
744#[derive(Clone, BorshSerialize, BorshDeserialize)]
745pub struct StoreConfig {
746    pub key: Key,
747    pub settings_uri: Option<String>,
748}
749impl StoreConfig {
750    pub fn from_account_info(a: &AccountInfo) -> Result<StoreConfig, ProgramError> {
751        let store: StoreConfig = try_from_slice_checked(
752            &a.data.borrow_mut(),
753            Key::StoreConfigV1,
754            MAX_STORE_CONFIG_V1_SIZE,
755        )?;
756
757        Ok(store)
758    }
759}
760
761#[repr(C)]
762#[derive(Clone, BorshSerialize, BorshDeserialize, Copy)]
763pub struct WhitelistedCreator {
764    pub key: Key,
765    pub address: Pubkey,
766    pub activated: bool,
767}
768
769impl WhitelistedCreator {
770    pub fn from_account_info(a: &AccountInfo) -> Result<WhitelistedCreator, ProgramError> {
771        let wc: WhitelistedCreator = try_from_slice_checked(
772            &a.data.borrow_mut(),
773            Key::WhitelistedCreatorV1,
774            MAX_WHITELISTED_CREATOR_SIZE,
775        )?;
776
777        Ok(wc)
778    }
779}
780
781#[repr(C)]
782#[derive(Clone, BorshSerialize, BorshDeserialize, Copy, Debug)]
783pub struct PrizeTrackingTicket {
784    pub key: Key,
785    pub metadata: Pubkey,
786    pub supply_snapshot: u64,
787    pub expected_redemptions: u64,
788    pub redemptions: u64,
789}
790
791impl PrizeTrackingTicket {
792    pub fn from_account_info(a: &AccountInfo) -> Result<PrizeTrackingTicket, ProgramError> {
793        let store: PrizeTrackingTicket = try_from_slice_checked(
794            &a.data.borrow_mut(),
795            Key::PrizeTrackingTicketV1,
796            MAX_PRIZE_TRACKING_TICKET_SIZE,
797        )?;
798
799        Ok(store)
800    }
801}
802
803#[repr(C)]
804#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Copy)]
805pub struct AmountRange(pub u64, pub u64);
806
807#[repr(C)]
808#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Copy)]
809pub enum TupleNumericType {
810    // So borsh won't listen to the actual numerical assignment of enum keys
811    // If you say U16 = 2 and it's the 2nd element in the enum and U8 = 1 and it's the first
812    // element, you would rightly assume encoding a 1 means U8 and a 2 means U16. However
813    // borsh assumes still that 0 = U8 and 1 = U16 because U8 appears first and U16 appears second in the enum.
814    // It simply ignores your manual assignment and goes purely off order in the enum.
815    // Because of how bad it is, we have to shove in these "padding" enums to make sure
816    // the values we set are the values it uses even though we dont use them for anything.
817    Padding0 = 0,
818    U8 = 1,
819    U16 = 2,
820    Padding1 = 3,
821    U32 = 4,
822    Padding2 = 5,
823    Padding3 = 6,
824    Padding4 = 7,
825    U64 = 8,
826}
827// Even though we dont use borsh for serialization to the chain, we do use this as an instruction argument
828// and that needs borsh.
829#[repr(C)]
830#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
831pub struct SafetyDepositConfig {
832    pub key: Key,
833    /// reverse lookup
834    pub auction_manager: Pubkey,
835    // only 255 safety deposits on vault right now but soon this will likely expand.
836    /// safety deposit order
837    pub order: u64,
838    pub winning_config_type: WinningConfigType,
839    pub amount_type: TupleNumericType,
840    pub length_type: TupleNumericType,
841    /// Tuple is (amount of editions or tokens given to people in this range, length of range)
842    pub amount_ranges: Vec<AmountRange>,
843    /// if winning config type is "Participation" then you use this to parameterize it.
844    pub participation_config: Option<ParticipationConfigV2>,
845    /// if winning config type is "Participation" then you use this to keep track of it.
846    pub participation_state: Option<ParticipationStateV2>,
847}
848
849pub struct AmountCumulativeReturn {
850    pub amount: u64,
851    pub cumulative_amount: u64,
852    pub total_amount: u64,
853}
854const ORDER_POSITION: usize = 33;
855const AUCTION_MANAGER_POSITION: usize = 1;
856const WINNING_CONFIG_POSITION: usize = 41;
857const AMOUNT_POSITION: usize = 42;
858const LENGTH_POSITION: usize = 43;
859const AMOUNT_RANGE_SIZE_POSITION: usize = 44;
860const AMOUNT_RANGE_FIRST_EL_POSITION: usize = 48;
861
862fn get_number_from_data(data: &Ref<&mut [u8]>, data_type: TupleNumericType, offset: usize) -> u64 {
863    return match data_type {
864        TupleNumericType::U8 => data[offset] as u64,
865        TupleNumericType::U16 => u16::from_le_bytes(*array_ref![data, offset, 2]) as u64,
866        TupleNumericType::U32 => u32::from_le_bytes(*array_ref![data, offset, 4]) as u64,
867        TupleNumericType::U64 => u64::from_le_bytes(*array_ref![data, offset, 8]),
868        _ => 0,
869    };
870}
871
872fn write_amount_type(
873    data: &mut RefMut<&mut [u8]>,
874    amount_type: TupleNumericType,
875    offset: usize,
876    range: &AmountRange,
877) {
878    match amount_type {
879        TupleNumericType::U8 => data[offset] = range.0 as u8,
880        TupleNumericType::U16 => *array_mut_ref![data, offset, 2] = (range.0 as u16).to_le_bytes(),
881        TupleNumericType::U32 => *array_mut_ref![data, offset, 4] = (range.0 as u32).to_le_bytes(),
882        TupleNumericType::U64 => *array_mut_ref![data, offset, 8] = range.0.to_le_bytes(),
883        _ => (),
884    }
885}
886
887fn write_length_type(
888    data: &mut RefMut<&mut [u8]>,
889    length_type: TupleNumericType,
890    offset: usize,
891    range: &AmountRange,
892) {
893    match length_type {
894        TupleNumericType::U8 => data[offset] = range.1 as u8,
895        TupleNumericType::U16 => *array_mut_ref![data, offset, 2] = (range.1 as u16).to_le_bytes(),
896        TupleNumericType::U32 => *array_mut_ref![data, offset, 4] = (range.1 as u32).to_le_bytes(),
897        TupleNumericType::U64 => *array_mut_ref![data, offset, 8] = range.1.to_le_bytes(),
898        _ => (),
899    }
900}
901
902impl SafetyDepositConfig {
903    /// Size of account with padding included
904    pub fn created_size(&self) -> usize {
905        return BASE_SAFETY_CONFIG_SIZE
906            + (self.amount_type as usize + self.length_type as usize) * self.amount_ranges.len();
907    }
908
909    pub fn get_order(a: &AccountInfo) -> u64 {
910        let data = a.data.borrow();
911        return u64::from_le_bytes(*array_ref![data, ORDER_POSITION, 8]);
912    }
913
914    pub fn get_auction_manager(a: &AccountInfo) -> Pubkey {
915        let data = a.data.borrow();
916        return Pubkey::new_from_array(*array_ref![data, AUCTION_MANAGER_POSITION, 32]);
917    }
918
919    pub fn get_amount_type(a: &AccountInfo) -> Result<TupleNumericType, ProgramError> {
920        let data = &a.data.borrow();
921
922        Ok(match data[AMOUNT_POSITION] {
923            1 => TupleNumericType::U8,
924            2 => TupleNumericType::U16,
925            4 => TupleNumericType::U32,
926            8 => TupleNumericType::U64,
927            _ => return Err(ProgramError::InvalidAccountData),
928        })
929    }
930
931    pub fn get_length_type(a: &AccountInfo) -> Result<TupleNumericType, ProgramError> {
932        let data = &a.data.borrow();
933
934        Ok(match data[LENGTH_POSITION] {
935            1 => TupleNumericType::U8,
936            2 => TupleNumericType::U16,
937            4 => TupleNumericType::U32,
938            8 => TupleNumericType::U64,
939            _ => return Err(ProgramError::InvalidAccountData),
940        })
941    }
942
943    pub fn get_amount_range_len(a: &AccountInfo) -> u32 {
944        let data = &a.data.borrow();
945
946        return u32::from_le_bytes(*array_ref![data, AMOUNT_RANGE_SIZE_POSITION, 4]);
947    }
948
949    pub fn get_winning_config_type(a: &AccountInfo) -> Result<WinningConfigType, ProgramError> {
950        let data = &a.data.borrow();
951
952        Ok(match data[WINNING_CONFIG_POSITION] {
953            0 => WinningConfigType::TokenOnlyTransfer,
954            1 => WinningConfigType::FullRightsTransfer,
955            2 => WinningConfigType::PrintingV1,
956            3 => WinningConfigType::PrintingV2,
957            4 => WinningConfigType::Participation,
958            _ => return Err(ProgramError::InvalidAccountData),
959        })
960    }
961
962    /// Basically finds what edition offset you should get from 0 for your FIRST edition,
963    /// and the amount of editions you should get. If not a PrintingV2 safety deposit, the edition offset
964    /// (the cumulative count of all amounts from all people up to yours) is (relatively) meaningless,
965    /// but the amount AT your point still represents the amount of tokens you would receive.
966    /// Stop at winner index determines what the total roll count will stop at, if none goes all the way through.
967    pub fn find_amount_and_cumulative_offset(
968        a: &AccountInfo,
969        index: u64,
970        stop_at_winner_index: Option<usize>,
971    ) -> Result<AmountCumulativeReturn, ProgramError> {
972        let data = &mut a.data.borrow();
973
974        let amount_type = SafetyDepositConfig::get_amount_type(a)?;
975
976        let length_type = SafetyDepositConfig::get_length_type(a)?;
977
978        let length_of_array = SafetyDepositConfig::get_amount_range_len(a) as usize;
979
980        let mut cumulative_amount: u64 = 0;
981        let mut total_amount: u64 = 0;
982        let mut amount: u64 = 0;
983        let mut current_winner_range_start: u64 = 0;
984        let mut offset = AMOUNT_RANGE_FIRST_EL_POSITION;
985        let mut not_found = true;
986        for _ in 0..length_of_array {
987            let amount_each_winner_gets = get_number_from_data(data, amount_type, offset);
988
989            offset += amount_type as usize;
990
991            let length_of_range = get_number_from_data(data, length_type, offset);
992
993            offset += length_type as usize;
994
995            let current_winner_range_end = current_winner_range_start
996                .checked_add(length_of_range)
997                .ok_or(MetaplexError::NumericalOverflowError)?;
998            let to_add = amount_each_winner_gets
999                .checked_mul(length_of_range)
1000                .ok_or(MetaplexError::NumericalOverflowError)?;
1001
1002            if index >= current_winner_range_start && index < current_winner_range_end {
1003                let up_to_winner = (index - current_winner_range_start)
1004                    .checked_mul(amount_each_winner_gets)
1005                    .ok_or(MetaplexError::NumericalOverflowError)?;
1006                cumulative_amount = cumulative_amount
1007                    .checked_add(up_to_winner)
1008                    .ok_or(MetaplexError::NumericalOverflowError)?;
1009                amount = amount_each_winner_gets;
1010
1011                not_found = false;
1012            } else if current_winner_range_start < index {
1013                cumulative_amount = cumulative_amount
1014                    .checked_add(to_add)
1015                    .ok_or(MetaplexError::NumericalOverflowError)?;
1016            }
1017
1018            if let Some(win_index) = stop_at_winner_index {
1019                let win_index_as_u64 = win_index as u64;
1020                if win_index_as_u64 >= current_winner_range_start
1021                    && win_index_as_u64 < current_winner_range_end
1022                {
1023                    let up_to_winner = (win_index_as_u64 - current_winner_range_start)
1024                        .checked_mul(amount_each_winner_gets)
1025                        .ok_or(MetaplexError::NumericalOverflowError)?;
1026                    total_amount = total_amount
1027                        .checked_add(up_to_winner)
1028                        .ok_or(MetaplexError::NumericalOverflowError)?;
1029                    break;
1030                } else if current_winner_range_start < win_index_as_u64 {
1031                    total_amount = total_amount
1032                        .checked_add(to_add)
1033                        .ok_or(MetaplexError::NumericalOverflowError)?;
1034                }
1035            } else {
1036                total_amount = total_amount
1037                    .checked_add(to_add)
1038                    .ok_or(MetaplexError::NumericalOverflowError)?;
1039            }
1040
1041            current_winner_range_start = current_winner_range_end
1042        }
1043
1044        if not_found {
1045            return Err(MetaplexError::WinnerIndexNotFound.into());
1046        }
1047
1048        Ok(AmountCumulativeReturn {
1049            cumulative_amount,
1050            total_amount,
1051            amount,
1052        })
1053    }
1054
1055    pub fn from_account_info(a: &AccountInfo) -> Result<SafetyDepositConfig, ProgramError> {
1056        let data = &mut a.data.borrow();
1057        if a.data_len() < BASE_SAFETY_CONFIG_SIZE {
1058            return Err(MetaplexError::DataTypeMismatch.into());
1059        }
1060
1061        if data[0] != Key::SafetyDepositConfigV1 as u8 {
1062            return Err(MetaplexError::DataTypeMismatch.into());
1063        }
1064
1065        let auction_manager = SafetyDepositConfig::get_auction_manager(a);
1066
1067        let order = SafetyDepositConfig::get_order(a);
1068
1069        let winning_config_type = SafetyDepositConfig::get_winning_config_type(a)?;
1070
1071        let amount_type = SafetyDepositConfig::get_amount_type(a)?;
1072
1073        let length_type = SafetyDepositConfig::get_length_type(a)?;
1074
1075        let length_of_array = SafetyDepositConfig::get_amount_range_len(a);
1076
1077        let mut offset: usize = AMOUNT_RANGE_FIRST_EL_POSITION;
1078        let mut amount_ranges = vec![];
1079        for _ in 0..length_of_array {
1080            let amount = get_number_from_data(data, amount_type, offset);
1081
1082            offset += amount_type as usize;
1083
1084            let length = get_number_from_data(data, length_type, offset);
1085
1086            amount_ranges.push(AmountRange(amount, length));
1087            offset += length_type as usize;
1088        }
1089
1090        let participation_config: Option<ParticipationConfigV2> = match data[offset] {
1091            0 => {
1092                offset += 1;
1093                None
1094            }
1095            1 => {
1096                let winner_constraint = match data[offset + 1] {
1097                    0 => WinningConstraint::NoParticipationPrize,
1098                    1 => WinningConstraint::ParticipationPrizeGiven,
1099                    _ => return Err(ProgramError::InvalidAccountData),
1100                };
1101                let non_winning_constraint = match data[offset + 2] {
1102                    0 => NonWinningConstraint::NoParticipationPrize,
1103                    1 => NonWinningConstraint::GivenForFixedPrice,
1104                    2 => NonWinningConstraint::GivenForBidPrice,
1105                    _ => return Err(ProgramError::InvalidAccountData),
1106                };
1107
1108                offset += 3;
1109
1110                let fixed_price: Option<u64> = match data[offset] {
1111                    0 => {
1112                        offset += 1;
1113                        None
1114                    }
1115                    1 => {
1116                        let number = u64::from_le_bytes(*array_ref![data, offset + 1, 8]);
1117                        offset += 9;
1118                        Some(number)
1119                    }
1120                    _ => return Err(ProgramError::InvalidAccountData),
1121                };
1122
1123                Some(ParticipationConfigV2 {
1124                    winner_constraint,
1125                    non_winning_constraint,
1126                    fixed_price,
1127                })
1128            }
1129            _ => return Err(ProgramError::InvalidAccountData),
1130        };
1131
1132        let participation_state: Option<ParticipationStateV2> = match data[offset] {
1133            0 => {
1134                // offset += 1;
1135                None
1136            }
1137            1 => {
1138                let collected_to_accept_payment =
1139                    u64::from_le_bytes(*array_ref![data, offset + 1, 8]);
1140                // offset += 9;
1141                Some(ParticipationStateV2 {
1142                    collected_to_accept_payment,
1143                })
1144            }
1145            _ => return Err(ProgramError::InvalidAccountData),
1146        };
1147
1148        // NOTE: Adding more fields? Uncomment the offset adjustments in participation state to keep
1149        // the math working.
1150
1151        Ok(SafetyDepositConfig {
1152            key: Key::SafetyDepositConfigV1,
1153            auction_manager,
1154            order,
1155            winning_config_type,
1156            amount_type,
1157            length_type,
1158            amount_ranges,
1159            participation_config,
1160            participation_state,
1161        })
1162    }
1163
1164    pub fn create(&self, a: &AccountInfo, auction_manager_key: &Pubkey) -> ProgramResult {
1165        let mut data = a.data.borrow_mut();
1166
1167        data[0] = Key::SafetyDepositConfigV1 as u8;
1168        // for whatever reason, copy_from_slice doesnt do jack here.
1169        let as_bytes = auction_manager_key.as_ref();
1170        for n in 0..32 {
1171            data[n + 1] = as_bytes[n];
1172        }
1173        *array_mut_ref![data, ORDER_POSITION, 8] = self.order.to_le_bytes();
1174        data[WINNING_CONFIG_POSITION] = self.winning_config_type as u8;
1175        data[AMOUNT_POSITION] = self.amount_type as u8;
1176        data[LENGTH_POSITION] = self.length_type as u8;
1177        *array_mut_ref![data, AMOUNT_RANGE_SIZE_POSITION, 4] =
1178            (self.amount_ranges.len() as u32).to_le_bytes();
1179        let mut offset: usize = AMOUNT_RANGE_FIRST_EL_POSITION;
1180        for range in &self.amount_ranges {
1181            write_amount_type(&mut data, self.amount_type, offset, range);
1182            offset += self.amount_type as usize;
1183            write_length_type(&mut data, self.length_type, offset, range);
1184            offset += self.length_type as usize;
1185        }
1186
1187        match &self.participation_config {
1188            Some(val) => {
1189                data[offset] = 1;
1190                data[offset + 1] = val.winner_constraint as u8;
1191                data[offset + 2] = val.non_winning_constraint as u8;
1192                offset += 3;
1193                match val.fixed_price {
1194                    Some(val) => {
1195                        data[offset] = 1;
1196                        *array_mut_ref![data, offset + 1, 8] = val.to_le_bytes();
1197                        offset += 9;
1198                    }
1199                    None => {
1200                        data[offset] = 0;
1201                        offset += 1;
1202                    }
1203                }
1204            }
1205            None => {
1206                data[offset] = 0;
1207                offset += 1;
1208            }
1209        }
1210
1211        match &self.participation_state {
1212            Some(val) => {
1213                data[offset] = 1;
1214                *array_mut_ref![data, offset + 1, 8] =
1215                    val.collected_to_accept_payment.to_le_bytes();
1216                //offset += 9;
1217            }
1218            None => {
1219                data[offset] = 0;
1220                //offset += 1
1221            }
1222        }
1223
1224        // NOTE: Adding more fields? Uncomment the offset adjustments in participation state to keep
1225        // the math working.
1226        Ok(())
1227    }
1228
1229    /// Smaller method for just participation state saving...saves cpu, and it's the only thing
1230    /// that will ever change on this model.
1231    pub fn save_participation_state(&mut self, a: &AccountInfo) {
1232        let mut data = a.data.borrow_mut();
1233        let mut offset: usize = AMOUNT_RANGE_FIRST_EL_POSITION
1234            + self.amount_ranges.len() * (self.amount_type as usize + self.length_type as usize);
1235
1236        offset += match &self.participation_config {
1237            Some(val) => {
1238                let mut total = 4;
1239                if val.fixed_price.is_some() {
1240                    total += 8;
1241                }
1242                total
1243            }
1244            None => 1,
1245        };
1246
1247        match &self.participation_state {
1248            Some(val) => {
1249                data[offset] = 1;
1250                *array_mut_ref![data, offset + 1, 8] =
1251                    val.collected_to_accept_payment.to_le_bytes();
1252                //offset += 9;
1253            }
1254            None => {
1255                data[offset] = 0;
1256                //offset += 1
1257            }
1258        }
1259
1260        // NOTE: Adding more fields? Uncomment the offset adjustments in participation state to keep
1261        // the math working.
1262    }
1263}
1264
1265#[repr(C)]
1266#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
1267pub struct AuctionWinnerTokenTypeTracker {
1268    pub key: Key,
1269    pub amount_type: TupleNumericType,
1270    pub length_type: TupleNumericType,
1271    /// Tuple is (amount of editions or tokens given to people in this range, length of range)
1272    pub amount_ranges: Vec<AmountRange>,
1273}
1274
1275impl AuctionWinnerTokenTypeTracker {
1276    pub fn created_size(&self, range_size: u64) -> usize {
1277        return BASE_TRACKER_SIZE
1278            + (self.amount_type as usize + self.length_type as usize) * range_size as usize;
1279    }
1280    pub fn from_account_info(
1281        a: &AccountInfo,
1282    ) -> Result<AuctionWinnerTokenTypeTracker, ProgramError> {
1283        let data = &mut a.data.borrow();
1284        if a.data_len() < BASE_TRACKER_SIZE {
1285            return Err(MetaplexError::DataTypeMismatch.into());
1286        }
1287
1288        if data[0] != Key::AuctionWinnerTokenTypeTrackerV1 as u8 {
1289            return Err(MetaplexError::DataTypeMismatch.into());
1290        }
1291
1292        let amount_type = AuctionWinnerTokenTypeTracker::get_amount_type(a)?;
1293
1294        let length_type = AuctionWinnerTokenTypeTracker::get_length_type(a)?;
1295
1296        let length_of_array = AuctionWinnerTokenTypeTracker::get_amount_range_len(a);
1297
1298        let mut offset: usize = 7;
1299        let mut amount_ranges = vec![];
1300        for _ in 0..length_of_array {
1301            let amount = get_number_from_data(data, amount_type, offset);
1302
1303            offset += amount_type as usize;
1304
1305            let length = get_number_from_data(data, length_type, offset);
1306
1307            amount_ranges.push(AmountRange(amount, length));
1308            offset += length_type as usize;
1309        }
1310
1311        Ok(AuctionWinnerTokenTypeTracker {
1312            key: Key::AuctionWinnerTokenTypeTrackerV1,
1313            amount_type,
1314            length_type,
1315            amount_ranges,
1316        })
1317    }
1318
1319    pub fn get_amount_type(a: &AccountInfo) -> Result<TupleNumericType, ProgramError> {
1320        let data = &a.data.borrow();
1321
1322        Ok(match data[1] {
1323            1 => TupleNumericType::U8,
1324            2 => TupleNumericType::U16,
1325            4 => TupleNumericType::U32,
1326            8 => TupleNumericType::U64,
1327            _ => return Err(ProgramError::InvalidAccountData),
1328        })
1329    }
1330
1331    pub fn get_length_type(a: &AccountInfo) -> Result<TupleNumericType, ProgramError> {
1332        let data = &a.data.borrow();
1333
1334        Ok(match data[2] {
1335            1 => TupleNumericType::U8,
1336            2 => TupleNumericType::U16,
1337            4 => TupleNumericType::U32,
1338            8 => TupleNumericType::U64,
1339            _ => return Err(ProgramError::InvalidAccountData),
1340        })
1341    }
1342
1343    pub fn get_amount_range_len(a: &AccountInfo) -> u32 {
1344        let data = &a.data.borrow();
1345
1346        return u32::from_le_bytes(*array_ref![data, 3, 4]);
1347    }
1348
1349    /// When there is a range where positive tokens are given in the handed in amount ranges,
1350    /// this counts as one unique token type, so we need to count that as a range where "1"
1351    /// new type is given. So we consider that a range of 1 and merge it into our existing ranges of
1352    /// totals here. So if you have 10 people each getting 1 token type and then you find out
1353    /// after a new safety deposit config is merged that the 3rd place person is the only person
1354    /// who gets it, you then end up with three ranges: 1st-2nd place getting 1 type, 3rd place getting 2 types,
1355    /// and 4th to 10th place getting 1 type.
1356    pub fn add_one_where_positive_ranges_occur(
1357        &mut self,
1358        amount_ranges: &mut Vec<AmountRange>,
1359    ) -> ProgramResult {
1360        let mut new_range: Vec<AmountRange> = vec![];
1361
1362        if self.amount_ranges.len() == 0 {
1363            self.amount_ranges = amount_ranges
1364                .iter()
1365                .map(|x| {
1366                    if x.0 > 0 {
1367                        return AmountRange(1, x.1);
1368                    } else {
1369                        return AmountRange(0, x.1);
1370                    }
1371                })
1372                .collect();
1373            return Ok(());
1374        } else if amount_ranges.len() == 0 {
1375            return Ok(());
1376        }
1377
1378        let mut my_ctr: usize = 0;
1379        let mut their_ctr: usize = 0;
1380        while my_ctr < self.amount_ranges.len() || their_ctr < amount_ranges.len() {
1381            // Cases:
1382            // 1. nothing in theirs - we win and pop on
1383            // 2. nothing in ours - they win and pop on
1384            // 3. our next range is shorter than their next range - we pop on a new range that is the length of our range and +1
1385            // 4. their next range is shorter than ours - we pop on a new range that is the length of theirs and +1 our range
1386            // In these cases where we don't use the entire range we need to not increase the counter but we do need to modify the object
1387            // length to indicate that it is now shorter, for the next iteration.
1388            // 5. Super degenerate case - they are of equal length
1389
1390            let mut to_add: u64 = 0;
1391            if their_ctr < amount_ranges.len() && amount_ranges[their_ctr].0 > 0 {
1392                to_add = 1;
1393            }
1394
1395            if my_ctr == self.amount_ranges.len() {
1396                new_range.push(AmountRange(to_add, amount_ranges[their_ctr].1));
1397                their_ctr += 1;
1398            } else if their_ctr == amount_ranges.len() {
1399                new_range.push(self.amount_ranges[my_ctr]);
1400                my_ctr += 1;
1401            } else if self.amount_ranges[my_ctr].1 > amount_ranges[their_ctr].1 {
1402                self.amount_ranges[my_ctr].1 = self.amount_ranges[my_ctr]
1403                    .1
1404                    .checked_sub(amount_ranges[their_ctr].1)
1405                    .ok_or(MetaplexError::NumericalOverflowError)?;
1406
1407                new_range.push(AmountRange(
1408                    self.amount_ranges[my_ctr]
1409                        .0
1410                        .checked_add(to_add)
1411                        .ok_or(MetaplexError::NumericalOverflowError)?,
1412                    amount_ranges[their_ctr].1,
1413                ));
1414
1415                their_ctr += 1;
1416                // dont increment my_ctr since i still have length to give
1417            } else if amount_ranges[their_ctr].1 > self.amount_ranges[my_ctr].1 {
1418                amount_ranges[their_ctr].1 = amount_ranges[their_ctr]
1419                    .1
1420                    .checked_sub(self.amount_ranges[my_ctr].1)
1421                    .ok_or(MetaplexError::NumericalOverflowError)?;
1422
1423                new_range.push(AmountRange(
1424                    self.amount_ranges[my_ctr]
1425                        .0
1426                        .checked_add(to_add)
1427                        .ok_or(MetaplexError::NumericalOverflowError)?,
1428                    self.amount_ranges[my_ctr].1,
1429                ));
1430
1431                my_ctr += 1;
1432                // dont increment their_ctr since they still have length to give
1433            } else if amount_ranges[their_ctr].1 == self.amount_ranges[my_ctr].1 {
1434                new_range.push(AmountRange(
1435                    self.amount_ranges[my_ctr]
1436                        .0
1437                        .checked_add(to_add)
1438                        .ok_or(MetaplexError::NumericalOverflowError)?,
1439                    self.amount_ranges[my_ctr].1,
1440                ));
1441                // Move them both in this degen case
1442                my_ctr += 1;
1443                their_ctr += 1;
1444            }
1445        }
1446
1447        self.amount_ranges = new_range;
1448        Ok(())
1449    }
1450
1451    pub fn save(&self, a: &AccountInfo) {
1452        let mut data = a.data.borrow_mut();
1453        data[0] = Key::AuctionWinnerTokenTypeTrackerV1 as u8;
1454        data[1] = self.amount_type as u8;
1455        data[2] = self.length_type as u8;
1456        *array_mut_ref![data, 3, 4] = (self.amount_ranges.len() as u32).to_le_bytes();
1457        let mut offset: usize = 7;
1458        for range in &self.amount_ranges {
1459            write_amount_type(&mut data, self.amount_type, offset, range);
1460            offset += self.amount_type as usize;
1461            write_length_type(&mut data, self.length_type, offset, range);
1462            offset += self.length_type as usize;
1463        }
1464    }
1465}
1466
1467#[repr(C)]
1468#[derive(Clone, BorshSerialize, BorshDeserialize, Copy)]
1469pub struct BidRedemptionTicket {
1470    // With BidRedemptionTicket is easier to hide it's legacy V1/V2 behind an internal facade,
1471    // since all of it's values are read directly off the array.
1472    pub key: Key,
1473}
1474
1475impl BidRedemptionTicket {
1476    pub fn check_ticket(
1477        bid_redemption_info: &AccountInfo,
1478        is_participation: bool,
1479        safety_deposit_config_info: Option<&AccountInfo>,
1480    ) -> ProgramResult {
1481        let bid_redemption_data = bid_redemption_info.data.borrow_mut();
1482        if bid_redemption_data[0] != Key::BidRedemptionTicketV1 as u8
1483            && bid_redemption_data[0] != Key::BidRedemptionTicketV2 as u8
1484        {
1485            return Err(MetaplexError::DataTypeMismatch.into());
1486        }
1487
1488        if bid_redemption_data[0] == Key::BidRedemptionTicketV1 as u8 {
1489            let mut participation_redeemed = false;
1490            if bid_redemption_data[1] == 1 {
1491                participation_redeemed = true;
1492            }
1493
1494            if is_participation && participation_redeemed {
1495                return Err(MetaplexError::BidAlreadyRedeemed.into());
1496            }
1497        } else if bid_redemption_data[0] == Key::BidRedemptionTicketV2 as u8 {
1498            // You can only redeem Full Rights Transfers one time per mint
1499            // You can only redeem Token Only Transfers one time per mint
1500            // You can only redeem PrintingV1 one time - you get all the printing tokens in one go
1501            // You can redeem PrintingV2s many times(once per edition given) - but we dont check these with this ticket
1502            // You can redeem Participation only once per mint
1503            // With the v2 of bid redemptions we establish a bitmask where each bit in order from left to right
1504            // represents the "order" field on the safety deposit box, with bit 0 representing safety deposit 0.
1505            // Flipping it to 1 means redeemed.
1506            match safety_deposit_config_info {
1507                Some(config) => {
1508                    let order = SafetyDepositConfig::get_order(config);
1509                    let (position, mask) =
1510                        BidRedemptionTicket::get_index_and_mask(&bid_redemption_data, order)?;
1511
1512                    if bid_redemption_data[position] & mask != 0 {
1513                        return Err(MetaplexError::BidAlreadyRedeemed.into());
1514                    }
1515                }
1516                None => return Err(MetaplexError::InvalidOperation.into()),
1517            }
1518        }
1519        Ok(())
1520    }
1521
1522    pub fn get_index_and_mask(
1523        data: &RefMut<&mut [u8]>,
1524        order: u64,
1525    ) -> Result<(usize, u8), ProgramError> {
1526        // add one because Key is at 0
1527        let mut offset = 42;
1528        if data[1] == 0 {
1529            // remove the lost option space
1530            offset -= 8;
1531        }
1532
1533        let u8_position = order
1534            .checked_div(8)
1535            .ok_or(MetaplexError::NumericalOverflowError)?
1536            .checked_add(offset)
1537            .ok_or(MetaplexError::NumericalOverflowError)?;
1538        let position_from_right = 7 - order
1539            .checked_rem(8)
1540            .ok_or(MetaplexError::NumericalOverflowError)?;
1541        let mask = u8::pow(2, position_from_right as u32);
1542
1543        Ok((u8_position as usize, mask))
1544    }
1545
1546    pub fn save(
1547        bid_redemption_info: &AccountInfo,
1548        participation_redeemed: bool,
1549        safety_deposit_config_info: Option<&AccountInfo>,
1550        winner_index: Option<usize>,
1551        auction_manager: Pubkey,
1552        auction_manager_version: Key,
1553    ) -> ProgramResult {
1554        // Saving on CPU in these large actions by avoiding borsh
1555        let data = &mut bid_redemption_info.data.borrow_mut();
1556        if data[0] == Key::BidRedemptionTicketV1 as u8
1557            || (data[0] == Key::Uninitialized as u8
1558                && auction_manager_version == Key::AuctionManagerV1)
1559        {
1560            let output = array_mut_ref![data, 0, 3];
1561
1562            let (key, participation_redeemed_ptr, _items_redeemed_ptr) =
1563                mut_array_refs![output, 1, 1, 1];
1564
1565            *key = [Key::BidRedemptionTicketV1 as u8];
1566
1567            if participation_redeemed {
1568                *participation_redeemed_ptr = [1];
1569            }
1570        } else if data[0] == Key::BidRedemptionTicketV2 as u8 || data[0] == Key::Uninitialized as u8
1571        {
1572            data[0] = Key::BidRedemptionTicketV2 as u8;
1573            let mut offset = 2;
1574
1575            if let Some(index) = winner_index {
1576                data[1] = 1;
1577                offset += 8;
1578                *array_mut_ref![data, 2, 8] = index.to_le_bytes();
1579            } else {
1580                data[1] = 0;
1581            }
1582
1583            let auction_manager_ptr = array_mut_ref![data, offset, 32];
1584
1585            auction_manager_ptr.copy_from_slice(auction_manager.as_ref());
1586
1587            match safety_deposit_config_info {
1588                Some(config) => {
1589                    let order = SafetyDepositConfig::get_order(config);
1590
1591                    let (position, mask) = BidRedemptionTicket::get_index_and_mask(data, order)?;
1592                    data[position] = data[position] | mask;
1593                }
1594                None => return Err(MetaplexError::InvalidOperation.into()),
1595            }
1596        }
1597
1598        Ok(())
1599    }
1600}