1use crate::{
2 error::MetaplexError,
3 state::{
4 get_auction_manager, AuctionManager, AuctionManagerStatus, BidRedemptionTicket, Key,
5 OriginalAuthorityLookup, Store, WhitelistedCreator, PREFIX,
6 },
7};
8use arrayref::array_ref;
9use borsh::BorshDeserialize;
10use ywpl_auction::{
11 instruction::end_auction_instruction,
12 processor::{
13 end_auction::EndAuctionArgs, AuctionData, AuctionDataExtended, AuctionState, BidderMetadata,
14 },
15};
16use mpl_token_metadata::{
17 instruction::update_metadata_accounts,
18 state::{Metadata, EDITION},
19};
20use ywpl_token_vault::{instruction::create_withdraw_tokens_instruction, state::Vault};
21use solana_program::{
22 account_info::AccountInfo,
23 borsh::try_from_slice_unchecked,
24 entrypoint::ProgramResult,
25 log::sol_log_compute_units,
26 msg,
27 program::{invoke, invoke_signed},
28 program_error::ProgramError,
29 program_pack::{IsInitialized, Pack},
30 pubkey::Pubkey,
31 system_instruction,
32 sysvar::{rent::Rent, Sysvar},
33};
34use spl_associated_token_account::get_associated_token_address;
35use spl_token::{
36 instruction::{set_authority, AuthorityType},
37 state::Account as SplAccount,
38};
39use std::convert::TryInto;
40
41pub fn get_amount_from_token_account(
43 token_account_info: &AccountInfo,
44) -> Result<u64, ProgramError> {
45 let data = token_account_info.try_borrow_data()?;
47 let amount_data = array_ref![data, 64, 8];
48 Ok(u64::from_le_bytes(*amount_data))
49}
50
51pub fn assert_initialized<T: Pack + IsInitialized>(
53 account_info: &AccountInfo,
54) -> Result<T, ProgramError> {
55 let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
56 if !account.is_initialized() {
57 Err(MetaplexError::Uninitialized.into())
58 } else {
59 Ok(account)
60 }
61}
62
63pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
64 if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
65 Err(MetaplexError::NotRentExempt.into())
66 } else {
67 Ok(())
68 }
69}
70
71pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
72 if account.owner != owner {
73 Err(MetaplexError::IncorrectOwner.into())
74 } else {
75 Ok(())
76 }
77}
78
79pub fn assert_signer(account_info: &AccountInfo) -> ProgramResult {
80 if !account_info.is_signer {
81 Err(ProgramError::MissingRequiredSignature)
82 } else {
83 Ok(())
84 }
85}
86
87pub fn assert_store_safety_vault_manager_match(
88 vault_key: &Pubkey,
89 safety_deposit_info: &AccountInfo,
90 vault_info: &AccountInfo,
91 token_vault_program: &Pubkey,
92) -> ProgramResult {
93 if vault_key != vault_info.key {
94 return Err(MetaplexError::AuctionManagerVaultMismatch.into());
95 }
96
97 let data = safety_deposit_info.data.borrow();
98 let vault_key_on_deposit = Pubkey::new_from_array(*array_ref![data, 1, 32]);
99 let token_mint_key = Pubkey::new_from_array(*array_ref![data, 33, 32]);
100
101 assert_derivation(
102 &token_vault_program,
103 safety_deposit_info,
104 &[
105 ywpl_token_vault::state::PREFIX.as_bytes(),
106 vault_info.key.as_ref(),
107 token_mint_key.as_ref(),
108 ],
109 )?;
110
111 if *vault_info.key != vault_key_on_deposit {
112 return Err(MetaplexError::SafetyDepositBoxVaultMismatch.into());
113 }
114
115 Ok(())
116}
117
118pub fn assert_at_least_one_creator_matches_or_store_public_and_all_verified(
119 program_id: &Pubkey,
120 auction_manager: &dyn AuctionManager,
121 metadata: &Metadata,
122 whitelisted_creator_info: &AccountInfo,
123 store_info: &AccountInfo,
124) -> ProgramResult {
125 let store = Store::from_account_info(store_info)?;
126 if store.public {
127 return Ok(());
128 }
129 if let Some(creators) = &metadata.data.creators {
130 let existing_whitelist_creator: WhitelistedCreator =
132 match WhitelistedCreator::from_account_info(whitelisted_creator_info) {
133 Ok(val) => val,
134 Err(_) => return Err(MetaplexError::InvalidWhitelistedCreator.into()),
135 };
136
137 if !existing_whitelist_creator.activated {
138 return Err(MetaplexError::WhitelistedCreatorInactive.into());
139 }
140
141 let mut found = false;
142 for creator in creators {
143 let (key, _) = Pubkey::find_program_address(
145 &[
146 PREFIX.as_bytes(),
147 program_id.as_ref(),
148 auction_manager.store().as_ref(),
149 creator.address.as_ref(),
150 ],
151 program_id,
152 );
153
154 if key == *whitelisted_creator_info.key {
155 found = true;
156 }
157
158 if !creator.verified {
159 return Err(MetaplexError::CreatorHasNotVerifiedMetadata.into());
160 }
161 }
162
163 if found {
164 return Ok(());
165 }
166 }
167 Err(MetaplexError::InvalidWhitelistedCreator.into())
168}
169
170pub fn assert_authority_correct(
171 auction_manager_authority: &Pubkey,
172 authority_info: &AccountInfo,
173) -> ProgramResult {
174 if auction_manager_authority != authority_info.key {
175 return Err(MetaplexError::AuctionManagerAuthorityMismatch.into());
176 }
177
178 assert_signer(authority_info)?;
179
180 Ok(())
181}
182
183pub fn assert_auction_is_ended_or_valid_instant_sale(
184 auction_info: &AccountInfo,
185 auction_extended_info: Option<&AccountInfo>,
186 bidder_metadata_info: &AccountInfo,
187 win_index: Option<usize>,
188) -> ProgramResult {
189 if AuctionData::get_state(auction_info)? == AuctionState::Ended {
190 return Ok(());
191 }
192
193 let instant_sale_price = auction_extended_info
194 .and_then(|info| AuctionDataExtended::get_instant_sale_price(&info.data.borrow()));
195
196 match instant_sale_price {
197 Some(instant_sale_price) => {
198 let winner_bid_price = win_index
199 .and_then(|i| AuctionData::get_winner_bid_amount_at(auction_info, i))
200 .unwrap_or(BidderMetadata::from_account_info(bidder_metadata_info)?.last_bid);
202
203 if winner_bid_price < instant_sale_price {
204 return Err(MetaplexError::AuctionHasNotEnded.into());
205 }
206 }
207 None => return Err(MetaplexError::AuctionHasNotEnded.into()),
208 }
209
210 Ok(())
211}
212
213#[inline(always)]
216pub fn create_or_allocate_account_raw<'a>(
217 program_id: Pubkey,
218 new_account_info: &AccountInfo<'a>,
219 rent_sysvar_info: &AccountInfo<'a>,
220 system_program_info: &AccountInfo<'a>,
221 payer_info: &AccountInfo<'a>,
222 size: usize,
223 signer_seeds: &[&[u8]],
224) -> Result<(), ProgramError> {
225 let rent = &Rent::from_account_info(rent_sysvar_info)?;
226 let required_lamports = rent
227 .minimum_balance(size)
228 .max(1)
229 .saturating_sub(new_account_info.lamports());
230
231 if required_lamports > 0 {
232 msg!("Transfer {} lamports to the new account", required_lamports);
233 invoke(
234 &system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports),
235 &[
236 payer_info.clone(),
237 new_account_info.clone(),
238 system_program_info.clone(),
239 ],
240 )?;
241 }
242
243 let accounts = &[new_account_info.clone(), system_program_info.clone()];
244
245 msg!("Allocate space for the account");
246 invoke_signed(
247 &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()),
248 accounts,
249 &[&signer_seeds],
250 )?;
251
252 msg!("Assign the account to the owning program");
253 invoke_signed(
254 &system_instruction::assign(new_account_info.key, &program_id),
255 accounts,
256 &[&signer_seeds],
257 )?;
258 msg!("Completed assignation!");
259
260 Ok(())
261}
262
263#[allow(clippy::too_many_arguments)]
264pub fn transfer_safety_deposit_box_items<'a>(
265 token_vault_program: AccountInfo<'a>,
266 destination: AccountInfo<'a>,
267 safety_deposit_box: AccountInfo<'a>,
268 safety_deposit_token_store: AccountInfo<'a>,
269 vault: AccountInfo<'a>,
270 fraction_mint: AccountInfo<'a>,
271 vault_authority: AccountInfo<'a>,
272 transfer_authority: AccountInfo<'a>,
273 rent: AccountInfo<'a>,
274 amount: u64,
275 signer_seeds: &[&[u8]],
276) -> ProgramResult {
277 invoke_signed(
278 &create_withdraw_tokens_instruction(
279 *token_vault_program.key,
280 *destination.key,
281 *safety_deposit_box.key,
282 *safety_deposit_token_store.key,
283 *vault.key,
284 *fraction_mint.key,
285 *vault_authority.key,
286 *transfer_authority.key,
287 amount,
288 ),
289 &[
290 token_vault_program,
291 destination,
292 safety_deposit_box,
293 safety_deposit_token_store,
294 vault,
295 fraction_mint,
296 vault_authority,
297 transfer_authority,
298 rent,
299 ],
300 &[&signer_seeds],
301 )?;
302
303 Ok(())
304}
305
306pub fn transfer_metadata_ownership<'a>(
307 token_metadata_program: AccountInfo<'a>,
308 metadata_info: AccountInfo<'a>,
309 update_authority: AccountInfo<'a>,
310 new_update_authority: AccountInfo<'a>,
311 signer_seeds: &[&[u8]],
312) -> ProgramResult {
313 invoke_signed(
314 &update_metadata_accounts(
315 *token_metadata_program.key,
316 *metadata_info.key,
317 *update_authority.key,
318 Some(*new_update_authority.key),
319 None,
320 None,
321 ),
322 &[
323 update_authority,
324 new_update_authority,
325 metadata_info,
326 token_metadata_program,
327 ],
328 &[&signer_seeds],
329 )?;
330
331 Ok(())
332}
333
334pub fn transfer_mint_authority<'a>(
335 new_authority_seeds: &[&[u8]],
336 new_authority_key: &Pubkey,
337 new_authority_info: &AccountInfo<'a>,
338 mint_info: &AccountInfo<'a>,
339 mint_authority_info: &AccountInfo<'a>,
340 token_program_info: &AccountInfo<'a>,
341) -> ProgramResult {
342 msg!("Setting mint authority");
343 invoke_signed(
344 &set_authority(
345 token_program_info.key,
346 mint_info.key,
347 Some(new_authority_key),
348 AuthorityType::MintTokens,
349 mint_authority_info.key,
350 &[&mint_authority_info.key],
351 )
352 .unwrap(),
353 &[
354 mint_authority_info.clone(),
355 mint_info.clone(),
356 token_program_info.clone(),
357 new_authority_info.clone(),
358 ],
359 &[new_authority_seeds],
360 )?;
361 msg!("Setting freeze authority");
362 invoke_signed(
363 &set_authority(
364 token_program_info.key,
365 mint_info.key,
366 Some(&new_authority_key),
367 AuthorityType::FreezeAccount,
368 mint_authority_info.key,
369 &[&mint_authority_info.key],
370 )
371 .unwrap(),
372 &[
373 mint_authority_info.clone(),
374 mint_info.clone(),
375 token_program_info.clone(),
376 new_authority_info.clone(),
377 ],
378 &[new_authority_seeds],
379 )?;
380
381 Ok(())
382}
383
384pub struct CommonRedeemReturn {
385 pub redemption_bump_seed: u8,
386 pub auction_manager: Box<dyn AuctionManager>,
387 pub cancelled: bool,
388 pub rent: Rent,
389 pub win_index: Option<usize>,
390 pub token_metadata_program: Pubkey,
391}
392
393pub struct CommonRedeemCheckArgs<'a> {
394 pub program_id: &'a Pubkey,
395 pub auction_manager_info: &'a AccountInfo<'a>,
396 pub safety_deposit_token_store_info: &'a AccountInfo<'a>,
397 pub destination_info: &'a AccountInfo<'a>,
398 pub bid_redemption_info: &'a AccountInfo<'a>,
399 pub safety_deposit_info: &'a AccountInfo<'a>,
400 pub vault_info: &'a AccountInfo<'a>,
401 pub auction_info: &'a AccountInfo<'a>,
402 pub auction_extended_info: Option<&'a AccountInfo<'a>>,
403 pub bidder_metadata_info: &'a AccountInfo<'a>,
404 pub bidder_info: &'a AccountInfo<'a>,
405 pub token_program_info: &'a AccountInfo<'a>,
406 pub token_vault_program_info: &'a AccountInfo<'a>,
407 pub token_metadata_program_info: &'a AccountInfo<'a>,
408 pub store_info: &'a AccountInfo<'a>,
409 pub rent_info: &'a AccountInfo<'a>,
410 pub safety_deposit_config_info: Option<&'a AccountInfo<'a>>,
411 pub is_participation: bool,
412 pub overwrite_win_index: Option<usize>,
415 pub user_provided_win_index: Option<Option<usize>>,
420 pub assert_bidder_signer: bool,
421 pub ignore_bid_redeemed_item_check: bool,
424}
425
426fn calculate_win_index(
427 bidder_info: &AccountInfo,
428 auction_info: &AccountInfo,
429 user_provided_win_index: Option<Option<usize>>,
430 overwrite_win_index: Option<usize>,
431) -> Result<Option<usize>, ProgramError> {
432 let mut win_index: Option<usize>;
433 if let Some(up_win_index) = user_provided_win_index {
435 if overwrite_win_index.is_none() {
439 if let Some(up_win_index_unwrapped) = up_win_index {
440 let winner = AuctionData::get_winner_at(auction_info, up_win_index_unwrapped);
441 if let Some(winner_key) = winner {
442 if winner_key != *bidder_info.key {
443 return Err(MetaplexError::WinnerIndexMismatch.into());
444 }
445 } else {
446 return Err(MetaplexError::WinnerIndexMismatch.into());
447 }
448 }
449 }
450
451 win_index = up_win_index;
456 } else {
457 win_index = AuctionData::get_is_winner(auction_info, bidder_info.key);
459 }
460
461 if let Some(index) = overwrite_win_index {
466 let winner_at = AuctionData::get_winner_at(auction_info, index);
467 if winner_at.is_some() {
468 return Err(MetaplexError::AuctioneerCantClaimWonPrize.into());
469 } else {
470 win_index = overwrite_win_index
471 }
472 }
473
474 Ok(win_index)
475}
476
477pub fn assert_safety_deposit_config_valid(
478 program_id: &Pubkey,
479 auction_manager_info: &AccountInfo,
480 safety_deposit_info: &AccountInfo,
481 safety_deposit_config_info: Option<&AccountInfo>,
482 auction_manager_key: &Key,
483) -> ProgramResult {
484 if let Some(config) = safety_deposit_config_info {
486 if *auction_manager_key == Key::AuctionManagerV2 {
487 assert_derivation(
488 program_id,
489 config,
490 &[
491 PREFIX.as_bytes(),
492 program_id.as_ref(),
493 auction_manager_info.key.as_ref(),
494 safety_deposit_info.key.as_ref(),
495 ],
496 )?;
497
498 if config.data.borrow()[0] != Key::SafetyDepositConfigV1 as u8 {
499 return Err(MetaplexError::DataTypeMismatch.into());
500 }
501 }
502 } else {
503 if *auction_manager_key == Key::AuctionManagerV2 {
505 return Err(MetaplexError::InvalidOperation.into());
506 }
507 }
508
509 Ok(())
510}
511
512#[allow(clippy::too_many_arguments)]
513pub fn common_redeem_checks(
514 args: CommonRedeemCheckArgs,
515) -> Result<CommonRedeemReturn, ProgramError> {
516 let CommonRedeemCheckArgs {
517 program_id,
518 auction_manager_info,
519 safety_deposit_token_store_info,
520 destination_info,
521 bid_redemption_info,
522 safety_deposit_info,
523 vault_info,
524 auction_info,
525 auction_extended_info,
526 bidder_metadata_info,
527 bidder_info,
528 token_program_info,
529 token_vault_program_info,
530 token_metadata_program_info,
531 rent_info,
532 store_info,
533 safety_deposit_config_info,
534 is_participation,
535 overwrite_win_index,
536 user_provided_win_index,
537 assert_bidder_signer,
538 ignore_bid_redeemed_item_check,
539 } = args;
540
541 let rent = &Rent::from_account_info(&rent_info)?;
542
543 let mut auction_manager: Box<dyn AuctionManager> = get_auction_manager(auction_manager_info)?;
544 let store_data = store_info.data.borrow();
545 let cancelled: bool;
546
547 let auction_program = Pubkey::new_from_array(*array_ref![store_data, 2, 32]);
548 let token_vault_program = Pubkey::new_from_array(*array_ref![store_data, 34, 32]);
549 let token_metadata_program = Pubkey::new_from_array(*array_ref![store_data, 66, 32]);
550 let token_program = Pubkey::new_from_array(*array_ref![store_data, 98, 32]);
551
552 let mut redemption_bump_seed: u8 = 0;
553 if overwrite_win_index.is_some() {
554 cancelled = false;
555
556 if *bidder_info.key != auction_manager.authority() {
557 return Err(MetaplexError::MustBeAuctioneer.into());
558 }
559 } else {
560 let bidder_metadata_data = bidder_metadata_info.data.borrow();
561 if bidder_metadata_data[80] == 0 {
562 cancelled = false
563 } else {
564 cancelled = true;
565 }
566 assert_owned_by(bidder_metadata_info, &auction_program)?;
567 assert_derivation(
568 &auction_program,
569 bidder_metadata_info,
570 &[
571 ywpl_auction::PREFIX.as_bytes(),
572 auction_program.as_ref(),
573 auction_info.key.as_ref(),
574 bidder_info.key.as_ref(),
575 "metadata".as_bytes(),
576 ],
577 )?;
578
579 let bidder_pubkey = Pubkey::new_from_array(*array_ref![bidder_metadata_data, 0, 32]);
580 if bidder_pubkey != *bidder_info.key {
581 return Err(MetaplexError::BidderMetadataBidderMismatch.into());
582 }
583 let auction_key = auction_manager.auction();
584 let redemption_path = [
585 PREFIX.as_bytes(),
586 auction_key.as_ref(),
587 bidder_metadata_info.key.as_ref(),
588 ];
589 let (redemption_key, actual_redemption_bump_seed) =
590 Pubkey::find_program_address(&redemption_path, &program_id);
591
592 redemption_bump_seed = actual_redemption_bump_seed;
593 if redemption_key != *bid_redemption_info.key {
594 return Err(MetaplexError::BidRedemptionMismatch.into());
595 }
596 }
597
598 let win_index = calculate_win_index(
599 bidder_info,
600 auction_info,
601 user_provided_win_index,
602 overwrite_win_index,
603 )?;
604
605 if !bid_redemption_info.data_is_empty()
606 && overwrite_win_index.is_none()
607 && !ignore_bid_redeemed_item_check
608 {
609 BidRedemptionTicket::check_ticket(
610 bid_redemption_info,
611 is_participation,
612 safety_deposit_config_info,
613 )?
614 }
615
616 if assert_bidder_signer {
617 assert_signer(bidder_info)?;
618 }
619
620 assert_owned_by(&destination_info, token_program_info.key)?;
621 assert_owned_by(&auction_manager_info, &program_id)?;
622 assert_owned_by(safety_deposit_token_store_info, token_program_info.key)?;
623 if !bid_redemption_info.data_is_empty() {
624 assert_owned_by(bid_redemption_info, &program_id)?;
625 }
626 assert_owned_by(safety_deposit_info, &token_vault_program)?;
627 assert_owned_by(vault_info, &token_vault_program)?;
628 assert_owned_by(auction_info, &auction_program)?;
629 assert_owned_by(store_info, &program_id)?;
630
631 assert_store_safety_vault_manager_match(
632 &auction_manager.vault(),
633 &safety_deposit_info,
634 &vault_info,
635 &token_vault_program,
636 )?;
637 assert_safety_deposit_config_valid(
638 program_id,
639 auction_manager_info,
640 safety_deposit_info,
641 safety_deposit_config_info,
642 &auction_manager.key(),
643 )?;
644 assert_rent_exempt(rent, &destination_info)?;
646
647 if auction_manager.auction() != *auction_info.key {
648 return Err(MetaplexError::AuctionManagerAuctionMismatch.into());
649 }
650
651 if *store_info.key != auction_manager.store() {
652 return Err(MetaplexError::AuctionManagerStoreMismatch.into());
653 }
654
655 if token_program != *token_program_info.key {
656 return Err(MetaplexError::AuctionManagerTokenProgramMismatch.into());
657 }
658
659 if token_vault_program != *token_vault_program_info.key {
660 return Err(MetaplexError::AuctionManagerTokenVaultProgramMismatch.into());
661 }
662
663 if token_metadata_program != *token_metadata_program_info.key {
664 return Err(MetaplexError::AuctionManagerTokenMetadataProgramMismatch.into());
665 }
666
667 assert_auction_is_ended_or_valid_instant_sale(
668 auction_info,
669 auction_extended_info,
670 bidder_metadata_info,
671 win_index,
672 )?;
673
674 auction_manager.set_status(AuctionManagerStatus::Disbursing);
676
677 Ok(CommonRedeemReturn {
678 redemption_bump_seed,
679 auction_manager,
680 cancelled,
681 rent: *rent,
682 win_index,
683 token_metadata_program,
684 })
685}
686
687pub struct CommonRedeemFinishArgs<'a> {
688 pub program_id: &'a Pubkey,
689 pub auction_manager: Box<dyn AuctionManager>,
690 pub auction_manager_info: &'a AccountInfo<'a>,
691 pub bidder_metadata_info: &'a AccountInfo<'a>,
692 pub rent_info: &'a AccountInfo<'a>,
693 pub system_info: &'a AccountInfo<'a>,
694 pub payer_info: &'a AccountInfo<'a>,
695 pub bid_redemption_info: &'a AccountInfo<'a>,
696 pub vault_info: &'a AccountInfo<'a>,
697 pub safety_deposit_config_info: Option<&'a AccountInfo<'a>>,
698 pub winning_index: Option<usize>,
699 pub redemption_bump_seed: u8,
700 pub bid_redeemed: bool,
701 pub participation_redeemed: bool,
702 pub winning_item_index: Option<usize>,
703 pub overwrite_win_index: Option<usize>,
704}
705#[allow(clippy::too_many_arguments)]
706pub fn common_redeem_finish(args: CommonRedeemFinishArgs) -> ProgramResult {
707 let CommonRedeemFinishArgs {
708 program_id,
709 auction_manager,
710 auction_manager_info,
711 bidder_metadata_info,
712 rent_info,
713 system_info,
714 payer_info,
715 bid_redemption_info,
716 vault_info,
717 safety_deposit_config_info,
718 winning_index,
719 redemption_bump_seed,
720 bid_redeemed,
721 participation_redeemed,
722 winning_item_index,
723 overwrite_win_index,
724 } = args;
725
726 if (bid_redeemed || participation_redeemed) && overwrite_win_index.is_none() {
727 let auction_key = auction_manager.auction();
728 let redemption_seeds = &[
729 PREFIX.as_bytes(),
730 auction_key.as_ref(),
731 bidder_metadata_info.key.as_ref(),
732 &[redemption_bump_seed],
733 ];
734
735 let token_type_count = Vault::get_token_type_count(vault_info)
736 .checked_div(8)
737 .ok_or(MetaplexError::NumericalOverflowError)?;
738
739 if bid_redemption_info.data_is_empty() {
740 create_or_allocate_account_raw(
741 *program_id,
742 &bid_redemption_info,
743 &rent_info,
744 &system_info,
745 &payer_info,
746 1 + 9 + 32 + 1 + token_type_count as usize,
747 redemption_seeds,
748 )?;
749 }
750
751 BidRedemptionTicket::save(
752 bid_redemption_info,
753 participation_redeemed,
754 safety_deposit_config_info,
755 winning_index,
756 *auction_manager_info.key,
757 auction_manager.key(),
758 )?;
759 }
760
761 msg!("About to pass through the eye of the needle");
762 sol_log_compute_units();
763
764 if bid_redeemed {
765 if let Some(index) = winning_index {
766 if let Some(item_index) = winning_item_index {
768 auction_manager.fast_save(auction_manager_info, index, item_index);
769 }
770 }
771 } else if participation_redeemed && auction_manager.key() == Key::AuctionManagerV2 {
772 if let Some(index) = winning_index {
775 auction_manager.fast_save(auction_manager_info, index, 0);
776 }
777 }
778
779 Ok(())
780}
781
782#[allow(clippy::too_many_arguments)]
783pub fn shift_authority_back_to_originating_user<'a>(
784 program_id: &Pubkey,
785 auction_manager: &dyn AuctionManager,
786 auction_manager_info: &AccountInfo<'a>,
787 master_metadata_info: &AccountInfo<'a>,
788 original_authority: &AccountInfo<'a>,
789 original_authority_lookup_info: &AccountInfo<'a>,
790 printing_mint_info: &AccountInfo<'a>,
791 token_program_info: &AccountInfo<'a>,
792 authority_seeds: &[&[u8]],
793) -> ProgramResult {
794 let auction_key = auction_manager.auction();
795 let original_authority_lookup_seeds = &[
796 PREFIX.as_bytes(),
797 auction_key.as_ref(),
798 master_metadata_info.key.as_ref(),
799 ];
800
801 let (expected_key, _) =
802 Pubkey::find_program_address(original_authority_lookup_seeds, &program_id);
803
804 if expected_key != *original_authority_lookup_info.key {
805 return Err(MetaplexError::OriginalAuthorityLookupKeyMismatch.into());
806 }
807
808 let original_authority_lookup: OriginalAuthorityLookup =
809 OriginalAuthorityLookup::from_account_info(original_authority_lookup_info)?;
810 if original_authority_lookup.original_authority != *original_authority.key {
811 return Err(MetaplexError::OriginalAuthorityMismatch.into());
812 }
813 transfer_mint_authority(
814 authority_seeds,
815 original_authority.key,
816 original_authority,
817 printing_mint_info,
818 auction_manager_info,
819 token_program_info,
820 )?;
821
822 Ok(())
823}
824
825#[inline(always)]
828pub fn spl_token_transfer<'a: 'b, 'b>(
829 source: AccountInfo<'a>,
830 destination: AccountInfo<'a>,
831 amount: u64,
832 authority: AccountInfo<'a>,
833 authority_signer_seeds: &'b [&'b [u8]],
834 token_program: AccountInfo<'a>,
835) -> ProgramResult {
836 let result = invoke_signed(
837 &spl_token::instruction::transfer(
838 token_program.key,
839 source.key,
840 destination.key,
841 authority.key,
842 &[],
843 amount,
844 )?,
845 &[source, destination, authority, token_program],
846 &[authority_signer_seeds],
847 );
848
849 result.map_err(|_| MetaplexError::TokenTransferFailed.into())
850}
851
852pub fn assert_edition_valid(
853 program_id: &Pubkey,
854 mint: &Pubkey,
855 edition_account_info: &AccountInfo,
856) -> ProgramResult {
857 let edition_seeds = &[
858 mpl_token_metadata::state::PREFIX.as_bytes(),
859 program_id.as_ref(),
860 &mint.as_ref(),
861 EDITION.as_bytes(),
862 ];
863 let (edition_key, _) = Pubkey::find_program_address(edition_seeds, program_id);
864 if edition_key != *edition_account_info.key {
865 return Err(MetaplexError::InvalidEditionKey.into());
866 }
867
868 Ok(())
869}
870
871pub fn spl_token_mint_to<'a: 'b, 'b>(
874 mint: AccountInfo<'a>,
875 destination: AccountInfo<'a>,
876 amount: u64,
877 authority: AccountInfo<'a>,
878 authority_signer_seeds: &'b [&'b [u8]],
879 token_program: AccountInfo<'a>,
880) -> ProgramResult {
881 let result = invoke_signed(
882 &spl_token::instruction::mint_to(
883 token_program.key,
884 mint.key,
885 destination.key,
886 authority.key,
887 &[],
888 amount,
889 )?,
890 &[mint, destination, authority, token_program],
891 &[authority_signer_seeds],
892 );
893 result.map_err(|_| MetaplexError::TokenMintToFailed.into())
894}
895
896pub fn assert_derivation(
897 program_id: &Pubkey,
898 account: &AccountInfo,
899 path: &[&[u8]],
900) -> Result<u8, ProgramError> {
901 let (key, bump) = Pubkey::find_program_address(&path, program_id);
902 if key != *account.key {
903 return Err(MetaplexError::DerivedKeyInvalid.into());
904 }
905 Ok(bump)
906}
907
908pub fn try_from_slice_checked<T: BorshDeserialize>(
909 data: &[u8],
910 data_type: Key,
911 data_size: usize,
912) -> Result<T, ProgramError> {
913 if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
914 || data.len() != data_size
915 {
916 return Err(MetaplexError::DataTypeMismatch.into());
917 }
918
919 let result: T = try_from_slice_unchecked(data)?;
920
921 Ok(result)
922}
923
924pub fn end_auction<'a: 'b, 'b>(
925 resource: Pubkey,
926 auction: AccountInfo<'a>,
927 authority: AccountInfo<'a>,
928 auction_program: AccountInfo<'a>,
929 clock: AccountInfo<'a>,
930 authority_signer_seeds: &'b [&'b [u8]],
931) -> ProgramResult {
932 invoke_signed(
933 &end_auction_instruction(
934 *auction_program.key,
935 *authority.key,
936 EndAuctionArgs {
937 resource,
938 reveal: None,
939 },
940 ),
941 &[auction, authority, auction_program, clock],
942 &[authority_signer_seeds],
943 )?;
944
945 Ok(())
946}
947
948pub fn assert_is_ata(
949 ata: &AccountInfo,
950 wallet: &Pubkey,
951 mint: &Pubkey,
952) -> Result<SplAccount, ProgramError> {
953 assert_owned_by(ata, &spl_token::id())?;
954 let ata_account: SplAccount = assert_initialized(ata)?;
955 assert_keys_equal(ata_account.owner, *wallet)?;
956 assert_keys_equal(ata_account.mint, *mint)?;
957 assert_keys_equal(get_associated_token_address(wallet, mint), *ata.key)?;
958 Ok(ata_account)
959}
960
961pub fn assert_keys_equal(key1: Pubkey, key2: Pubkey) -> ProgramResult {
962 if key1 != key2 {
963 Err(MetaplexError::PublicKeyMismatch.into())
964 } else {
965 Ok(())
966 }
967}