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};
16pub 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 + 32 + 8 + 4 + 32*MAX_INDEXED_ELEMENTS; pub const MAX_METADATA_PER_CACHE: usize = 10;
32pub const MAX_AUCTION_CACHE_SIZE: usize = 1 + 32 + 8 + 4 + 32*MAX_METADATA_PER_CACHE + 32 + 32 + 32; pub const MAX_AUCTION_MANAGER_V2_SIZE: usize = 1 + 32 + 32 + 32 + 32 + 32 + 1 + 1 + 8 + 200; pub const MAX_STORE_SIZE: usize = 2 + 32 + 32 + 32 + 32 + 100; pub const MAX_STORE_CONFIG_V1_SIZE: usize = 2 + 200 + 100; pub 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 +32 + 8 + 1 + 1 + 1 + 4 + 1 + 1 + 1 + 9 + 1 + 8 + 20; #[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 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 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 expected_redemptions: derived_results.total_amount,
341 winning_config_type: SafetyDepositConfig::get_winning_config_type(config)?,
342 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 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 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 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 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 pub safety_config_items_validated: u64,
550 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 pub collected_to_accept_payment: u64,
566}
567
568#[repr(C)]
569#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
570pub struct ParticipationConfigV2 {
571 pub winner_constraint: WinningConstraint,
575
576 pub non_winning_constraint: NonWinningConstraint,
581
582 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 TokenOnlyTransfer,
618 FullRightsTransfer,
622 PrintingV1,
625 PrintingV2,
627 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 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#[repr(C)]
830#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
831pub struct SafetyDepositConfig {
832 pub key: Key,
833 pub auction_manager: Pubkey,
835 pub order: u64,
838 pub winning_config_type: WinningConfigType,
839 pub amount_type: TupleNumericType,
840 pub length_type: TupleNumericType,
841 pub amount_ranges: Vec<AmountRange>,
843 pub participation_config: Option<ParticipationConfigV2>,
845 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 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 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 None
1136 }
1137 1 => {
1138 let collected_to_accept_payment =
1139 u64::from_le_bytes(*array_ref![data, offset + 1, 8]);
1140 Some(ParticipationStateV2 {
1142 collected_to_accept_payment,
1143 })
1144 }
1145 _ => return Err(ProgramError::InvalidAccountData),
1146 };
1147
1148 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 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 }
1218 None => {
1219 data[offset] = 0;
1220 }
1222 }
1223
1224 Ok(())
1227 }
1228
1229 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 }
1254 None => {
1255 data[offset] = 0;
1256 }
1258 }
1259
1260 }
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 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 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 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 } 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 } 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 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 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 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 let mut offset = 42;
1528 if data[1] == 0 {
1529 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 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}