ywpl_plex/
utils.rs

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
41/// Cheap method to just grab amount from token account, instead of deserializing entire thing
42pub fn get_amount_from_token_account(
43    token_account_info: &AccountInfo,
44) -> Result<u64, ProgramError> {
45    // TokeAccount layout:   mint(32), owner(32), ...
46    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
51/// assert initialized account
52pub 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        // does it exist? It better!
131        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            // Now find at least one creator that can make this pda in the list
144            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                // Possible case in an open auction
201                .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/// Create account almost from scratch, lifted from
214/// https://github.com/solana-labs/solana-program-library/blob/7d4873c61721aca25464d42cc5ef651a7923ca79/associated-token-account/program/src/processor.rs#L51-L98
215#[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    // If this is being called by the auctioneer to pull prizes out they overwrite the win index
413    // they would normally get if they themselves bid for whatever win index they choose.
414    pub overwrite_win_index: Option<usize>,
415    // In newer endpoints, to conserve CPU and make way for 10,000 person auctions,
416    // client must specify win index and then we simply check if the address matches for O(1) lookup vs O(n)
417    // scan. This is an option so older actions which rely on the O(n) lookup because we can't change their call structure
418    // can continue to work.
419    pub user_provided_win_index: Option<Option<usize>>,
420    pub assert_bidder_signer: bool,
421    // For printing v2, the edition pda is what essentially forms a backstop for bad bidders. We do not need this additional
422    // check which isn't accurate anyway when one winning config item has an amount > 1.
423    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    // User provided us with an option of an option telling us what if anything they won. We need to validate.
434    if let Some(up_win_index) = user_provided_win_index {
435        // check that this person is the winner they say they are. Only if not doing an override of win index,
436        // which we know likely wont match bidder info and is simply checking below that you arent stealing a prize.
437
438        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        // Notice if overwrite win index is some, this gets wiped anyway in the if statement below.
452        // If not, it becomes the win index going forward as we have validated the user is either
453        // saying they won nothing (Participation redemption) or they won something
454        // and they weren't lying.
455        win_index = up_win_index;
456    } else {
457        // Legacy system where we O(n) scan the bid index to find the winner index. CPU intensive.
458        win_index = AuctionData::get_is_winner(auction_info, bidder_info.key);
459    }
460
461    // This means auctioneer is attempting to pull goods out of the system, and is attempting to set
462    // the win index for themselves. Has a different field because it has different logic - mainly
463    // just checking to make sure you arent claiming from someone who won. Supersedes normal user provided
464    // logic.
465    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 using v2, you must have one and it must be the right address and type
485    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        // V2s MUST provide a safety deposit config, v1s it's optional (because its unused)
504        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    // looking out for you!
645    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    // No-op if already set.
675    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            // There should never be a winning item index for V2...its a nonsensical concept, but we set it anyway for backwards compatibility.
767            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        // AV2s can be saved (and set to disbursing) even in open edition auctions...a bug in V1 that we were okay with
773        // for speed. AV1s never get set to disbursing in Open Edition auctions.
774        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// TODO due to a weird stack access violation bug we had to remove the args struct from this method
826// to get redemptions working again after integrating new Auctions program. Try to bring it back one day
827#[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
871// TODO due to a weird stack access violation bug we had to remove the args struct from this method
872// to get redemptions working again after integrating new Auctions program. Try to bring it back one day.
873pub 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}